2008-11-04 5 views
79

Wie kann ich CRTP in C++ verwenden, um den Overhead von virtuellen Elementfunktionen zu vermeiden?CRTP, um dynamischen Polymorphismus zu vermeiden

+5

Hallo, vielen Dank für Ihre Antworten. Ich verstehe die Mechanismen von CRTP. Das Problem ist jedoch, dass es nicht wirklich Polymorphismus ist, d. H. Wenn ich X: Basis {} und Y: Basis {} habe, sind X und Y nicht verwandte Typen. d. h. ich kann sie nicht im selben Container halten oder sie sogar verwenden, ohne den abgeleiteten Typ zu kennen. –

+11

Dieser _is_ Polymorphismus, beit statisch - Wenn Sie CRTP verwenden, erstellen Sie einen templateten Client-Code, der (statisch) polymorpher Code ist: Sie müssen kein Byte Code ändern, um mit einem anderen Typ zu arbeiten. – xtofl

Antwort

1

Ich musste CRTP nachschlagen. Nachdem ich das getan hatte, fand ich etwas über Static Polymorphism. Ich vermute, dass dies die Antwort auf Ihre Frage ist.

Es stellt sich heraus, dass ATL dieses Muster recht umfangreich verwendet.

-3

This Wikipedia Antwort hat alles was Sie brauchen. Nämlich:

template <class Derived> struct Base 
{ 
    void interface() 
    { 
     // ... 
     static_cast<Derived*>(this)->implementation(); 
     // ... 
    } 

    static void static_func() 
    { 
     // ... 
     Derived::static_sub_func(); 
     // ... 
    } 
}; 

struct Derived : Base<Derived> 
{ 
    void implementation(); 
    static void static_sub_func(); 
}; 

Obwohl ich nicht weiß, wie viel das tatsächlich kauft Sie. Der Overhead eines virtuellen Funktionsaufruf (Compiler abhängig, natürlich):

  • Speicher: Ein Funktionszeiger pro virtuelle Funktion
  • Laufzeit: Ein Funktionszeiger Anruf

Während der Aufwand für CRTP statische Polymorphismus ist:

  • Speicher: Duplication of Base pro Vorlage Instanziierung
  • Laufzeit: Eine func tionszeigerruf + was auch immer static_cast macht
+4

Eigentlich ist die Duplizierung von Base pro Template Instanziierung eine Illusion, weil der Compiler den Speicher der Base und der abgeleiteten in eine einzelne Struktur für Sie zusammenführen wird (es sei denn, Sie haben noch eine Vtable).Der Aufruf des Funktionszeigers wird auch vom Compiler optimiert (der Teil static_cast). –

+16

Übrigens ist Ihre Analyse von CRTP falsch. Es sollte sein: Speicher: Nichts, wie Dean Michael sagte. Laufzeit: Ein (schneller) statischer Funktionsaufruf, nicht virtuell, was der eigentliche Sinn der Übung ist. static_cast tut nichts, es ermöglicht nur den Code zu kompilieren. –

+2

Mein Punkt ist, dass der Basiscode in allen Vorlageninstanzen dupliziert werden wird (die ganze Verschmelzung, von der Sie sprechen). Ähnlich wie eine Vorlage mit nur einer Methode, die auf dem Vorlagenparameter beruht. alles andere ist in einer Basisklasse besser, ansonsten wird es mehrmals ("zusammengeführt") eingezogen. – user23167

19

Ich habe nach anständigen Diskussionen über CRTP selbst gesucht. Todd Veldhuizens Techniques for Scientific C++ ist eine großartige Ressource für dieses (1.3) und viele andere fortgeschrittene Techniken wie Expression-Templates.

Auch fand ich, dass Sie die meisten von Coplien ursprünglichen C++ Gems Artikel bei Google Bücher lesen konnten. Vielleicht ist das immer noch so.

+0

@fizzer Ich habe den Teil gelesen, den Sie vorschlagen, aber immer noch nicht verstehen, was die Vorlage doppelte Summe (Matrix & A); kauft Sie im Vergleich zu Vorlage doppelte Summe (Was auch immer & A); –

+0

@AntonDaneyko Wenn sie aufgerufen Bei einer Basisinstanz wird die Summe der Basisklasse aufgerufen, zB "Fläche einer Form" mit Standardimplementierung als ob es ein Quadrat wäre. Das Ziel von CRTP in diesem Fall ist es, die am weitesten abgeleitete Implementierung "Bereich von ein Trapez "usw. während man immer noch auf das Trapez als eine Form verweisen kann, bis abgeleitetes Verhalten benötigt wird. Grundsätzlich, wann immer man normalerweise dynamische oder virtuelle Methoden benötigt. –

120

Es gibt zwei Möglichkeiten.

Die erste ist durch die Schnittstelle statisch für die Struktur von Typen spezifizieren:

template <class Derived> 
struct base { 
    void foo() { 
    static_cast<Derived *>(this)->foo(); 
    }; 
}; 

struct my_type : base<my_type> { 
    void foo(); // required to compile. 
}; 

struct your_type : base<your_type> { 
    void foo(); // required to compile. 
}; 

Die zweite wird durch die Verwendung der Referenz-to-Base oder pointer-to-Base-Idiom zu vermeiden und zu tun die Verkabelung zur Kompilierzeit. Unter Verwendung der obigen Definition können Sie Template-Funktionen haben, wie diese aussehen:

template <class T> // T is deduced at compile-time 
void bar(base<T> & obj) { 
    obj.foo(); // will do static dispatch 
} 

struct not_derived_from_base { }; // notice, not derived from base 

// ... 
my_type my_instance; 
your_type your_instance; 
not_derived_from_base invalid_instance; 
bar(my_instance); // will call my_instance.foo() 
bar(your_instance); // will call your_instance.foo() 
bar(invalid_instance); // compile error, cannot deduce correct overload 

So die Struktur/Interface-Definition kombiniert und die Kompilierung-Typ Abzug in Ihre Funktionen können Sie statische Versand statt dynamischen Dispatch tun. Dies ist die Essenz des statischen Polymorphismus.

+1

gutes Beispiel, danke. – ttvd

+14

Ausgezeichnete Antwort –

+0

Danke, hab Ihr Code zu verwenden – Yola