2013-10-21 4 views
6

Betrachten Sie den folgenden Code-Schnipsel:Wie kann ich einen virtuellen Anruf vermeiden, wenn ich den Typ kenne?

struct Base { virtual void func() { } }; 
struct Derived1 : Base { void func() override { print("1"); } }; 
struct Derived2 : Base { void func() override { print("2"); } }; 

class Manager { 
    std::vector<std::unique_ptr<Base>> items; 

    public: 
     template<class T> void add() { items.emplace_back(new T); } 
     void funcAll() { for(auto& i : items) i->func(); } 
}; 

int main() { 
    Manager m; 
    m.add<Derived1>(); 
    m.add<Derived2>(); 
    m.funcAll(); // prints "1" and "2" 
}; 

Ich virtual Versand, um mit der richtigen override Methode aus einem std::vector polymorphen Objekten zu nennen.

Allerdings weiß ich, welchen Typ die polymorphen Objekte sind, da ich dies in Manager::add<T> angeben.

Meine Idee war es, einen virtual Anruf zu vermeiden, indem Sie die Adresse der Mitgliedsfunktion T::func() nehmen und direkt irgendwo speichern. Das ist jedoch unmöglich, da ich es als void* speichern und in Manager::funcAll() zurückgeben müsste, aber ich habe in diesem Moment keine Typinformationen.

Meine Frage ist: es scheint, dass ich weitere Informationen in dieser Situation für Polymorphismus als üblich habe (der Benutzer gibt den abgeleiteten Typen T in Manager::add<T>) - ist es eine Möglichkeit, diese Art Informationen verwenden, um ein scheinbar unnötigen virtual zu verhindern Anruf? (Ein Benutzer sollte in der Lage sein, ihre eigenen Klassen zu erstellen, die von Base in seinem Code ableiten, aber.)

+1

"Gibt es eine Möglichkeit, diese Art von Informationen zu verwenden, um einen scheinbar unnötigen virtuellen Anruf zu verhindern?" Lösche diese Information nicht? ('unique_ptr ' tippt hier das Löschen ein). In jedem Fall "die Adresse der Member-Funktion' T :: func() 'zu nehmen und sie direkt irgendwo zu speichern" ist so ziemlich das Gleiche wie ein virtueller Aufruf, außer mit viel mehr Arbeit von Ihrer Seite. –

+0

@ R.MartinhoFernandes: Ich verstehe. Ich sehe jedoch keine andere Möglichkeit, Typen zu speichern, die von 'T' abstammen, selbst nach Angabe des Typs in' Manager :: add '- denken Sie daran, dass der Benutzer seine eigenen Typen definieren soll, die von' Base' abgeleitet sind Ich weiß nicht, was diese Typen sein werden. –

+1

Also, was Sie wollen, ist in der Sprache einfach unmöglich zu erreichen (ein Compiler könnte jedoch eine solche Optimierung in diesem Beispielcode vornehmen, aber ich bezweifle, dass Sie das in der Praxis finden). Es ist eine klassische "Sie können Ihren Kuchen nicht essen und haben es auch" -Situation: Entweder löschen Sie die Typen, oder Sie behalten die Typen. –

Antwort

8

aber ich weiß, was die polymorphen Objekte des Typ sind, da ich, dass in Manager::add<T> angeben.

Nein, tun Sie nicht. Innerhalb add kennen Sie den Typ des Objekts, das hinzugefügt wird; Sie können jedoch Objekte anderer Typen hinzufügen, wie Sie es in Ihrem Beispiel tun. Es gibt keine Möglichkeit für funcAll, die Typen der Elemente statisch zu bestimmen, es sei denn, Sie parametrisieren Manager, um nur einen Typ zu behandeln.

Wenn Sie den Typ nicht kannte, dann könnte man die Funktion nicht virtuell nennen:

i->T::func(); 

Aber zu wiederholen, können Sie nicht den Typ statisch hier bestimmen.

+1

Würde das wirklich einen nicht-virtuellen Anruf machen? Ich dachte, dass die Syntax nur zum Aufruf von Funktionen aus Ihren Basisklassen funktioniert. –

+1

@ R.MartinhoFernandes: Ja, das ist die Syntax für einen nicht virtuellen Anruf. Demonstration: http://ideone.com/JljtCy –

0

Wenn ich gut verstehe, möchten Sie Ihre Add-Methode, die die Klasse des Objekts erhält, um die richtige Funktion in Ihrem Vektor abhängig von dieser Objektklasse zu speichern. Ihr Vektor enthält nur Funktionen, keine weiteren Informationen über die Objekte.

Sie möchten den virtuellen Aufruf "lösen", bevor er aufgerufen wird. Dies ist vielleicht in folgendem Fall interessant: Die Funktion wird dann oft aufgerufen, weil Sie nicht den Aufwand haben, jedes Mal das Virtuelle zu lösen.

Sie möchten vielleicht einen ähnlichen Prozess verwenden als was "virtuell" tut, indem Sie eine "virtuelle Tabelle" verwenden. Die Implementierung von virtual erfolgt auf niedriger Ebene, also ziemlich schnell im Vergleich zu allem, was Sie sich vorstellen, also sollten die Funktionen VIELE Male aufgerufen werden, bevor es interessant wird.

0

Ein Trick, der manchmal in dieser Art von Situation helfen kann, ist, den Vektor nach Typ zu sortieren (Sie sollten das Wissen des in der Funktion add() verfügbaren Typs verwenden, um die Reihenfolge der Elemente zu erzwingen) ist sonst nicht wichtig. Wenn Sie hauptsächlich über den Vektor iterieren, um eine virtuelle Funktion aufzurufen, hilft dies dem Verzweigungsprädiktor der CPU, das Ziel des Aufrufs vorherzusagen.Alternativ können Sie für jeden Typ in Ihrem Manager separate Vektoren pflegen und diese wiederum durchlaufen, was einen ähnlichen Effekt hat.

Der Optimierer Ihres Compilers kann Ihnen auch bei dieser Art von Code helfen, insbesondere wenn er die Profilgeführte Optimierung (POGO) unterstützt. Compiler können Aufrufe in bestimmten Situationen de-virtualisieren, oder mit POGO können Dinge in der generierten Assembly tun, um die Verzweigungsprädiktor der CPU zu helfen, wie Test für die gebräuchlichsten Typen und führen Sie einen direkten Aufruf für diejenigen mit einem Fallback zu einem indirekten Aufruf für die weniger häufige Typen.

Hier die Ergebnisse eines Testprogramms, das die Leistungsvorteile der Sortierung nach Art zeigt, Manager ist Ihre Version hält Manager2 eine Hash-Tabelle von Vektoren durch typeid indiziert:

Derived1::count = 50043000, Derived2::count = 49957000 
class Manager::funcAll took 714ms 
Derived1::count = 50043000, Derived2::count = 49957000 
class Manager2::funcAll took 274ms 
Derived1::count = 50043000, Derived2::count = 49957000 
class Manager2::funcAll took 273ms 
Derived1::count = 50043000, Derived2::count = 49957000 
class Manager::funcAll took 714ms 

Prüfregeln:

#include <iostream> 
#include <vector> 
#include <memory> 
#include <random> 
#include <unordered_map> 
#include <typeindex> 
#include <chrono> 

using namespace std; 
using namespace std::chrono; 

static const int instanceCount = 100000; 
static const int funcAllIterations = 1000; 
static const int numTypes = 2; 

struct Base { virtual void func() = 0; }; 
struct Derived1 : Base { static int count; void func() override { ++count; } }; 
int Derived1::count = 0; 
struct Derived2 : Base { static int count; void func() override { ++count; } }; 
int Derived2::count = 0; 

class Manager { 
    vector<unique_ptr<Base>> items; 

public: 
    template<class T> void add() { items.emplace_back(new T); } 
    void funcAll() { for (auto& i : items) i->func(); } 
}; 

class Manager2 { 
    unordered_map<type_index, vector<unique_ptr<Base>>> items; 

public: 
    template<class T> void add() { items[type_index(typeid(T))].push_back(make_unique<T>()); } 
    void funcAll() { 
     for (const auto& type : items) { 
      for (auto& i : type.second) { 
       i->func(); 
      } 
     } 
    } 
}; 

template<typename Man> 
void Test() { 
    mt19937 engine; 
    uniform_int_distribution<int> d(0, numTypes - 1); 

    Derived1::count = 0; 
    Derived2::count = 0; 

    Man man; 
    for (auto i = 0; i < instanceCount; ++i) { 
     switch (d(engine)) { 
     case 0: man.add<Derived1>(); break; 
     case 1: man.add<Derived2>(); break; 
     } 
    } 

    auto startTime = high_resolution_clock::now(); 
    for (auto i = 0; i < funcAllIterations; ++i) { 
     man.funcAll(); 
    } 
    auto endTime = high_resolution_clock::now(); 

    cout << "Derived1::count = " << Derived1::count << ", Derived2::count = " << Derived2::count << "\n" 
     << typeid(Man).name() << "::funcAll took " << duration_cast<milliseconds>(endTime - startTime).count() << "ms" << endl; 
} 

int main() { 
    Test<Manager>(); 
    Test<Manager2>(); 

    Test<Manager2>(); 
    Test<Manager>(); 
} 
Verwandte Themen