Nehmen wir an, ich möchte eine Reihe von Klassen erstellen, die jeweils eine Member-Funktion mit der gleichen Funktion haben. Rufen wir die Funktion
auf %Vor%Ich möchte schließlich alle diese Klassen in den gleichen Container legen, so dass ich sie durchlaufen kann und jedes 'doYourJob ()'
ausführen kannDie naheliegende Lösung besteht darin, eine abstrakte Klasse mit der Funktion
zu erstellen %Vor%aber ich zögere das zu tun. Dies ist ein zeitaufwändiges Programm und eine virtuelle Funktion würde es erheblich verschlimmern. Außerdem ist diese Funktion das einzige, was die Klassen gemeinsam haben und doYourJob wird für jede Klasse völlig anders ausgeführt.
Gibt es eine Möglichkeit, die Verwendung einer abstrakten Klasse mit einer virtuellen Funktion zu vermeiden, oder muss ich sie aufheben?
Virtuelle Funktionen kosten nicht viel. Sie sind ein indirekter Aufruf, im Grunde wie ein Funktionszeiger. Was sind die Leistungskosten von eine virtuelle Methode in einer C ++ - Klasse haben?
Wenn Sie in einer Situation sind, in der jeder Zyklus pro Aufruf zählt, dh Sie arbeiten nur sehr wenig im Funktionsaufruf und rufen ihn von Ihrer inneren Schleife aus in einer leistungskritischen Anwendung, benötigen Sie wahrscheinlich eine andere Annäherung insgesamt.
Wenn die Geschwindigkeit benötigt, sollten Sie eine "type (-identifying) -Nummer" in die Objekte einbetten und mithilfe einer switch-Anweisung den typspezifischen Code auswählen. Dies kann den Funktionsaufruf-Overhead vollständig vermeiden - nur einen lokalen Sprung ausführen. Du wirst nicht schneller als das werden. Eine Kosten (in Bezug auf Wartbarkeit, Neukompilierungsabhängigkeiten usw.) besteht darin, die Lokalisierung (im Switch) der typspezifischen Funktionalität zu erzwingen.
UMSETZUNG
%Vor%LEISTUNGSERGEBNISSE
Auf meinem Linux-System:
%Vor%Dies deutet darauf hin, dass ein Inline-Typ-Nummern-geschalteter Ansatz etwa (1,28 - 0,23) / (0,344 - 0,23) = 9,2 mal so schnell ist. Natürlich ist das spezifisch für das genau getestete System / Compilerflags & amp; Version usw., aber im Allgemeinen indikativ.
KOMMENTARE RE VIRTUAL DISPATCH
Es muss jedoch gesagt werden, dass virtuelle Funktionsaufruf-Overheads etwas sind, das selten signifikant ist, und dann nur für häufig genannte triviale Funktionen (wie Getter und Setter). Selbst dann können Sie vielleicht eine einzige Funktion bereitstellen, um eine ganze Menge von Dingen auf einmal zu erhalten und einzustellen, wodurch die Kosten minimiert werden. Die Leute machen sich viel zu viele Gedanken über den virtuellen Versand - machen Sie also das Profiling, bevor Sie peinliche Alternativen finden. Das Hauptproblem bei ihnen ist, dass sie einen Out-of-Line-Funktionsaufruf ausführen, obwohl sie auch den ausgeführten Code delokalisieren, der die Cache-Auslastungsmuster ändert (zum besseren oder (häufig) schlechteren).
Ich befürchte, dass eine Reihe von dynamic_cast
in einer Schleife die Leistung schlechter verschleimt als eine virtuelle Funktion. Wenn Sie sie alle in einen Container werfen wollen, müssen sie einen Typ gemeinsam haben. Sie können also auch eine reine virtuelle Basisklasse mit dieser Methode erstellen.
In diesem Kontext ist die Verteilung der virtuellen Funktionen nicht allzu umfangreich: eine Vtable-Suche, eine Anpassung des angegebenen this
-Zeigers und ein indirekter Aufruf.
Wenn die Leistung so wichtig ist, können Sie möglicherweise für jeden Subtyp einen separaten Container verwenden und jeden Container unabhängig verarbeiten. Wenn die Reihenfolge wichtig ist, würden Sie so viele Backflips machen, dass der virtuelle Versand wahrscheinlich schneller ist.
Wenn Sie alle diese Objekte in demselben Container speichern wollen, müssen Sie entweder einen heterogenen Containertyp (langsam und teuer) schreiben, und Sie müssen einen Container mit void *
s (yuck!), oder die Klassen müssen durch Vererbung miteinander verwandt sein. Wenn Sie sich für eine der ersten beiden Optionen entscheiden, müssen Sie über eine gewisse Logik verfügen, um jedes Element im Container zu betrachten, herauszufinden, um welchen Typ es sich handelt, und dann die entsprechende doYourJob()
-Implementierung aufzurufen läuft im Wesentlichen auf Vererbung hinaus.
Ich empfehle dringend, zuerst den einfachen, direkten Ansatz der Vererbung auszuprobieren. Wenn das schnell genug ist, ist das großartig! Sie sind fertig. Wenn nicht, versuchen Sie es mit einem anderen Schema. Vermeiden Sie niemals ein nützliches Sprachfeature aufgrund der Kosten, es sei denn, Sie haben einen guten, harten Beweis dafür, dass die Kosten zu hoch sind.
Tags und Links c++ code-organization