Simulated dynamische Bindung (es gibt auch andere Verwendungen von CRTP) zum, wenn der Basisklasse versteht sich als polymorph zu sein, aber Kunden tatsächlich nur über eine bestimmte abgeleitete Klasse kümmern. Zum Beispiel könnten Sie Klassen haben, die eine Schnittstelle zu einigen plattformspezifischen Funktionen darstellen, und jede gegebene Plattform wird immer nur eine Implementierung benötigen. Der Punkt des Musters besteht darin, die Basisklasse zu templatisieren, so dass, obwohl es mehrere abgeleitete Klassen gibt, die Basisklasse zur Kompilierzeit weiß, welche verwendet wird.
Es hilft Ihnen nicht, wenn Sie wirklich Laufzeit-Polymorphismus benötigen, wie zum Beispiel wenn Sie einen Container von AbstractWidget*
haben, kann jedes Element eine von mehreren abgeleiteten Klassen sein, und Sie müssen darüber iterieren. In CRTP (oder einem beliebigen Vorlagencode) sind base<derived1>
und base<derived2>
nicht miteinander in Beziehung stehende Klassen. Daher sind auch derived1
und derived2
. Es gibt keinen dynamischen Polymorphismus zwischen ihnen, es sei denn, sie haben eine andere gemeinsame Basisklasse, aber dann sind Sie wieder da, wo Sie mit virtuellen Aufrufen begonnen haben.
Sie könnten eine Beschleunigung erzielen, indem Sie Ihren Vektor durch mehrere Vektoren ersetzen: einen für jede abgeleitete Klasse, den Sie kennen, und einen generischen, wenn Sie später neue abgeleitete Klassen hinzufügen und den Container nicht aktualisieren. Dann fügt addWidget einige (langsam) typeid
Überprüfung oder einen virtuellen Aufruf an das Widget, um das Widget zum richtigen Container hinzuzufügen, und hat möglicherweise einige Überladungen für, wenn der Aufrufer die Laufzeitklasse kennt. Achten Sie darauf, nicht versehentlich eine Unterklasse von WidgetIKnowAbout
in den Vektor WidgetIKnowAbout*
einzufügen. fooAll
und barAll
können Schleife über jeden Container der Reihe nach (schnell) Anrufe zu nicht-virtuellen fooImpl
und barImpl
Funktionen machen, die dann inline sind. Sie schleifen dann über den hoffentlich viel kleineren AbstractWidget*
Vektor und rufen die virtuellen foo
oder bar
Funktionen auf.
Es ist ein bisschen chaotisch und nicht pure-OO, aber wenn fast alle Ihre Widgets zu Klassen gehören, die Ihr Container kennt, dann sehen Sie möglicherweise eine Leistungssteigerung.
Wenn die meisten Widgets zu Klassen gehören, die Ihr Container möglicherweise nicht kennt (weil sie sich beispielsweise in verschiedenen Bibliotheken befinden), können Sie möglicherweise keine Inlining-Operationen ausführen (es sei denn, Ihr dynamischer Linker kann inline sein t). Sie könnten den virtuellen Anruf-Overhead fallen lassen, indem Sie sich mit Mitgliedsfunktionszeigern herumärgern, aber der Gewinn wäre mit ziemlicher Sicherheit vernachlässigbar oder sogar negativ. Der größte Teil des Overheads eines virtuellen Anrufs liegt im Anruf selbst, nicht in der virtuellen Suche, und Anrufe über Funktionszeiger sind nicht inline.
Sehen Sie es sich anders an: Wenn der Code inline sein soll, bedeutet dies, dass der tatsächliche Maschinencode für die verschiedenen Typen unterschiedlich sein muss. Dies bedeutet, dass Sie entweder mehrere Schleifen oder eine Schleife mit einem Schalter benötigen, da der Maschinencode im ROM bei jedem Durchlauf durch die Schleife nicht geändert werden kann, je nachdem, welcher Zeiger aus einer Sammlung gezogen wurde.
Nun, ich denke, vielleicht könnte das Objekt einige Asm-Code enthalten, die die Schleife in RAM kopiert, markiert ausführbar und springt in. Aber das ist keine C++ - Memberfunktion. Und es kann nicht portabel gemacht werden. Und es wäre wahrscheinlich nicht einmal schnell, was mit dem Kopieren und der Icache-Entwertung. Deshalb existieren virtuelle Anrufe ...
Nur ein kleiner Nit - Konstruktoren in C++ kann nicht virtuell sein :) –
oops, sorry, behoben –
Können Sie Ihre Basisklassen "AbstractWidget" "WidgetCollection" ändern, oder werden von einem anderen Entwickler/Community entwickelt? – umlcat