2012-03-28 8 views
18

Ich verstehe, dass dynamische/statische Polymorphie hängt von der Anwendung Design und Anforderungen ab. Ist es jedoch ratsam, IMMER statischen Polymorphismus über Dynamik zu wählen, wenn möglich? Insbesondere kann ich die folgenden zwei Design-Wahl in meiner Anwendung sehen, von denen beide scheinen gegen beraten werden:Dyamic gegen statische Polymorphie in C++: Was ist vorzuziehen?

  1. Implementieren Static Polymorphismus CRTP mit: No VTable Overhead-Lookup, während immer noch eine Schnittstelle in Form von Vorlage bereitstellt Basisklasse. Aber, verwendet eine Menge Schalter und static_cast die richtige Klasse/Methode zuzugreifen, die gefährlich ist

  2. Dynamische Polymorphism: Schnittstellen implementieren (rein virtuellen Klassen), Lookup-Kosten assoziieren noch triviale Funktionen wie Accessoren/Mutatoren

Meine Anwendung ist sehr zeitkritisch, deshalb bin ich für statische Polymorphie. Aber müssen Sie wissen, ob die Verwendung zu vieler static_cast ein Hinweis auf schlechtes Design ist und wie Sie dies ohne Latenz vermeiden können.

EDIT: Danke für die Einsicht. In einem konkreten Fall, welcher davon ist ein besserer Ansatz?

class IMessage_Type_1 
{ 
    virtual long getQuantity() =0; 
... 
} 
class Message_Type_1_Impl: public IMessage_Type_1 
{ 
    long getQuantity() { return _qty;} 
... 
} 

ODER

template <class T> 
class TMessage_Type_1 
{ 
    long getQuantity() { return static_cast<T*>(this)->getQuantity(); } 
... 
} 
class Message_Type_1_Impl: public TMessage_Type_1<Message_Type_1_Impl> 
{ 
    long getQuantity() { return _qty; } 
... 
} 

Hinweis, dass es mehr Mutatoren/Accessoren in jeder Klasse, und ich brauche eine Schnittstelle in meiner Anwendung zu spezifizieren. Bei statischem Polymorphismus wechsle ich nur einmal - um den Nachrichtentyp zu erhalten. Im dynamischen Polymorphismus verwende ich jedoch virtuelle Funktionen für den JEDEN Methodenaufruf. Ist es nicht ein Fall, statische Poly zu verwenden? Ich glaube, static_cast in CRTP ist ziemlich sicher und keine Leistungseinbußen (kompilierzeitgebunden)?

+4

Eine Switch-Case-Struktur hat die gleiche Komplexität wie die Verwendung einer Vtable. – user877329

+0

Ich bin mir nicht sicher, warum du denkst, dass du bei statischen Polymorphismen viele statische_Schaltungen und Schalter hast - kannst du einen Beispielcode zeigen? –

+1

@MichaelAnderson: Sie werden das bekommen, wenn Sie versuchen, statischen Polymorphismus in einer Situation zu verwenden, die dynamischen Polymorphismus erfordert. In diesem Fall ist es fast sicher besser, dynamischen Polymorphismus zu verwenden, anstatt ihn neu zu erfinden. –

Antwort

8

Ein Schalter ist nichts anderes als eine Abfolge von Sprüngen, die - nachdem sie optimiert wurden - ein Sprung zu einer Adresse wird, die von einer Tabelle nachgeschlagen wird. Genau wie ein virtueller Funktionsaufruf ist.

Wenn Sie abhängig von einem Typ springen müssen, müssen Sie zuerst den Typ auswählen. Wenn die Auswahl zur Kompilierzeit nicht möglich ist (hauptsächlich weil sie von der Eingabe abhängt), müssen Sie immer zwei Operationen ausführen: Wählen Sie & springen. Das syntaktische Werkzeug, das Sie verwenden, ändert die Leistung nicht, da dasselbe optimiert wird.

In der Tat sind Sie neu erfinden die V-Tabelle.

+0

Ich stimme zu, dass Switch-Fall im Grunde eine VTable ist. Im dynamischen Polymorphismus verwenden wir jedoch virtuals für die Methode EVERY, während wir im statischen Modus für den abgeleiteten Typ einmal den Switch und den Cast verwenden müssen, danach gibt es keine Indirektion. Ein Beispiel in der Frage hinzugefügt. Gibt es einen besseren Weg, dies zu implementieren? – vid

+0

@vid: Wenn der Aufrufer darf über den abgeleiteten Typ wissen, was ist der Zweck der Polymorphismus überhaupt? In diesem Fall kann der Aufrufer direkt mit der abgeleiteten Klasse interagieren, ohne Abstraktionen, Schnittstellen usw. Scheint, dass sowohl Polymorphismus als auch Vererbung hier redundant sind. Wenn der Aufrufer den abgeleiteten Typ nicht kennen darf, dann sollten die Typ-Casting- und Methoden-Dispatching bei jedem Aufruf innerhalb der Basisklasse durchgeführt werden. vtable ist die optimale Lösung für solche Dispositionen. CRPT ist das richtige Werkzeug für die Optimierung von Aufrufen von Base zu abgeleiteten Klasse (nur) – user396672

+0

Ok, ich schätze Ihren Standpunkt. Was ich versuchte, war eine Schnittstelle mit CRTP anzugeben. Wenn also andere Entwickler die Bibliothek erweitern möchten, erben sie die Basisklasse erneut mithilfe von CRTP. Wenn sie keine Methode implementieren können, wird ein Kompilierungsfehler ausgelöst. Gleiche Logik wie eine reine virtuelle Basisklasse, aber ohne den Vtable-Overhead. Gibt es ein Idiom, an das ich denken kann, das dies passender umsetzt? – vid

6

Sie sehen die Designprobleme, die mit dem reinen Template-basierten Polymorphismus verbunden sind. Während eine virtuelle Basisklasse eine ziemlich gute Vorstellung davon gibt, was von einer abgeleiteten Klasse erwartet wird, wird dies bei stark gestaffelten Designs viel schwieriger. Dies lässt sich leicht durch Einführung eines Syntaxfehlers bei Verwendung einer der Boost-Bibliotheken demonstrieren.

Auf der anderen Seite haben Sie Angst vor Leistungsproblemen bei der Verwendung von virtuellen Funktionen. Der Nachweis, dass dies ein Problem sein wird, ist viel schwieriger.

IMHO ist dies keine Frage. Bleiben Sie bei den virtuellen Funktionen, bis Sie etwas anderes angeben. Virtuelle Funktionsaufrufe sind viel schneller als die meisten Leute denken (das Aufrufen einer Funktion aus einer dynamisch verknüpften Bibliothek fügt auch eine indirekte Ebene hinzu. Niemand scheint darüber nachzudenken).

Ich würde nur ein Vorlagenentwurf betrachten, wenn es den Code leichter lesbar macht (generische Algorithmen), Sie verwenden einen der wenigen bekannten Fälle, die mit virtuellen Funktionen (numerische Algorithmen) langsam sind oder Sie haben es bereits als eine Leistung identifiziert Engpass.

+0

Vorlagen erzwingen (zur Kompilierzeit) auch Prädikate über das Typsystem. Wenn ich einen 'std :: vector ' benutze, weiß ich, dass niemand versehentlich ein 'B' hineinlegt. Der eingefügte Typ entspricht immer dem Typ, den ich extrahieren möchte. –

+0

Danke @ebo.Ich definiere virtuelle Funktionen als Engpass in meiner Anwendung, bearbeite die Frage, um den Overhead klarer zu machen – vid

14

Statische und dynamische Polymorphie werden entwickelt, um verschiedene Probleme zu lösen, so dass es selten Fälle gibt, in denen beides angebracht wäre. In solchen Fällen, dynamische Polymorphie wird in einem flexibleren und einfacher Design zu führen. Aber die meiste Zeit wird die Wahl aus anderen Gründen offensichtlich sein.

Eine grobe Kategorisierung der beiden: virtuelle Funktionen ermöglichen verschiedene Implementierungen für eine gemeinsame Schnittstelle; Vorlagen erlauben verschiedene Schnittstellen für eine gemeinsame Implementierung.

+0

'virtuelle Funktionen erlauben verschiedene Implementierungen für eine gemeinsame Schnittstelle; Templates erlauben unterschiedliche Schnittstellen für eine gemeinsame Implementierung. "Das macht keinen Sinn. Statischer Polymorphismus erlaubt zwar zwei verschiedenen Klassen, ihre eigene Implementierung zu erstellen, aber die Methode hat denselben Namen und wird in beiden Fällen von der Basisklasse aufgerufen. – johnbakers

+1

@Fellowshee In der gebräuchlichsten Verwendung bietet eine Vorlagenklasse eine andere Schnittstelle, aber die gleiche Implementierung für jeden Implementierungstyp: z. 'std :: vector ' liefert eine 'push_back (double)' Funktion, wobei 'std :: vector ' 'push_back (int)' liefert. Verschiedene Schnittstellen; gleiche Implementierung. Vererbung von einem virtuellen 'push_back (double)' würde verschiedene Implementierungen erlauben, würde aber keine andere Schnittstelle erlauben. –

1

Statische Polimorphie kann einen wesentlichen Vorteil bieten, wenn die aufgerufene Methode vom Compiler inline gesetzt werden kann. Zum Beispiel, wenn die virtuelle Methode sieht wie folgt aus:

protected: 
virtual bool is_my_class_fast_enough() override {return true;} 

dann statisch polimophism die bevorzugte Art und Weise (auch anders sein sollte, sollte die Methode ehrlich sein und falsch :) zurück.

"True" virtueller Anruf (in den meisten Fällen) kann nicht inline sein.

Andere Unterschiede (wie zusätzliche Indirektion im VTable Anruf) sind vernachlässigbare

[EDIT]

Wenn Sie jedoch wirklich Runtime Polymorphismus müssen (wenn der Anrufer nicht wissen sollte die Implementierung der Methode und daher die Methode kann nicht auf der Seite des Aufrufers inlined sein) dann nicht Vtable neu erfinden (wie Emilio Garavaglia erwähnt), einfach verwenden.