Ich habe diese Frage erhalten, als ich einen Kommentar zur Codeüberprüfung erhielt, der besagt, dass virtuelle Funktionen nicht inline sein müssen.
Ich dachte, virtuelle Inline-Funktionen könnten in Szenarien nützlich sein, in denen Funktionen direkt für Objekte aufgerufen werden. Aber das Gegenargument kam mir in den Sinn: Warum sollte man virtuell definieren und dann Objekte verwenden, um Methoden aufzurufen?
Ist es am besten, keine virtuellen Inline-Funktionen zu verwenden, da diese sowieso fast nie erweitert werden?
Code-Snippet, das ich für die Analyse verwendet habe:
class Temp
{
public:
virtual ~Temp()
{
}
virtual void myVirtualFunction() const
{
cout<<"Temp::myVirtualFunction"<<endl;
}
};
class TempDerived : public Temp
{
public:
void myVirtualFunction() const
{
cout<<"TempDerived::myVirtualFunction"<<endl;
}
};
int main(void)
{
TempDerived aDerivedObj;
//Compiler thinks it's safe to expand the virtual functions
aDerivedObj.myVirtualFunction();
//type of object Temp points to is always known;
//does compiler still expand virtual functions?
//I doubt compiler would be this much intelligent!
Temp* pTemp = &aDerivedObj;
pTemp->myVirtualFunction();
return 0;
}
pTemp->myVirtualFunction()
könnte , dass dies als nicht virtueller Anruf gelöst werden könnte, könnte es diesen Anruf inline haben. Dieser referenzierte Aufruf wird von g ++ 3.4.2 eingefügt:TempDerived & pTemp = aDerivedObj; pTemp.myVirtualFunction();
Ihr Code ist nicht.Antworten:
Virtuelle Funktionen können manchmal eingebunden werden. Ein Auszug aus der ausgezeichneten C ++ - FAQ :
quelle
C ++ 11 wurde hinzugefügt
final
. Dies ändert die akzeptierte Antwort: Es ist nicht mehr erforderlich, die genaue Klasse des Objekts zu kennen. Es reicht aus zu wissen, dass das Objekt mindestens den Klassentyp hat, in dem die Funktion als endgültig deklariert wurde:quelle
icc
scheint es laut diesem Link zu tun.Es gibt eine Kategorie von virtuellen Funktionen, bei denen es immer noch sinnvoll ist, sie inline zu haben. Betrachten Sie den folgenden Fall:
Der Aufruf zum Löschen von 'base' führt einen virtuellen Aufruf zum Aufrufen des korrekten abgeleiteten Klassendestruktors durch. Dieser Aufruf ist nicht inline. Da jedoch jeder Destruktor seinen übergeordneten Destruktor aufruft (der in diesen Fällen leer ist), kann der Compiler diese Aufrufe einbinden, da sie die Basisklassenfunktionen nicht virtuell aufrufen.
Das gleiche Prinzip gilt für Basisklassenkonstruktoren oder für alle Funktionen, bei denen die abgeleitete Implementierung auch die Basisklassenimplementierung aufruft.
quelle
Ich habe Compiler gesehen, die keine V-Tabelle ausgeben, wenn überhaupt keine Nicht-Inline-Funktion vorhanden ist (und dann in einer Implementierungsdatei anstelle eines Headers definiert ist). Sie würden Fehler wie
missing vtable-for-class-A
oder ähnliches werfen , und Sie würden höllisch verwirrt sein, wie ich es war.Dies entspricht zwar nicht dem Standard, es kommt jedoch vor, dass mindestens eine virtuelle Funktion nicht in den Header eingefügt wird (wenn nur der virtuelle Destruktor), damit der Compiler an dieser Stelle eine vtable für die Klasse ausgeben kann. Ich weiß, dass es mit einigen Versionen von passiert
gcc
.Wie bereits erwähnt, können virtuelle Inline-Funktionen manchmal von Vorteil sein , aber natürlich werden Sie sie meistens verwenden, wenn Sie den dynamischen Typ des Objekts nicht kennen, da dies
virtual
in erster Linie der ganze Grund dafür war .Der Compiler kann dies jedoch nicht vollständig ignorieren
inline
. Es hat eine andere Semantik als die Beschleunigung eines Funktionsaufrufs. Die implizite Inline für klasseninterne Definitionen ist der Mechanismus, mit dem Sie die Definition in den Header einfügen können: Nurinline
Funktionen können im gesamten Programm mehrmals definiert werden, ohne dass Regeln verletzt werden. Am Ende verhält es sich so, als hätten Sie es im gesamten Programm nur einmal definiert, obwohl Sie den Header mehrmals in verschiedene miteinander verknüpfte Dateien eingefügt haben.quelle
Nun, tatsächlich können virtuelle Funktionen immer inline sein , solange sie statisch miteinander verbunden sind: Nehmen wir an, wir haben eine abstrakte Klasse
Base
mit einer virtuellen FunktionF
und abgeleiteten KlassenDerived1
undDerived2
:Ein hypotetischer Anruf
b->F();
(mitb
TypBase*
) ist offensichtlich virtuell. Aber Sie (oder der Compiler ...) könnten es so umschreiben (nehmen wir an, estypeof
handelt sich um einetypeid
ähnliche Funktion, die einen Wert zurückgibt, der in a verwendet werden kannswitch
).Während wir noch RTTI für das benötigen
typeof
, kann der Aufruf effektiv eingebunden werden, indem im Grunde die vtable in den Anweisungsstrom eingebettet und der Aufruf für alle beteiligten Klassen spezialisiert wird. Dies könnte auch verallgemeinert werden, indem nur wenige Klassen (z. B. nurDerived1
) spezialisiert werden:quelle
Das Inline-Markieren einer virtuellen Methode hilft bei der weiteren Optimierung der virtuellen Funktionen in den folgenden zwei Fällen:
Seltsamerweise wiederkehrendes Vorlagenmuster ( http://www.codeproject.com/Tips/537606/Cplusplus-Prefer-Curiously-Recurring-Template-Patt )
Ersetzen virtueller Methoden durch Vorlagen ( http://www.di.unipi.it/~nids/docs/templates_vs_inheritance.html )
quelle
Inline macht wirklich nichts - es ist ein Hinweis. Der Compiler ignoriert es möglicherweise oder integriert ein Aufrufereignis ohne Inline, wenn er die Implementierung sieht und diese Idee mag. Wenn es um Codeklarheit geht, sollte die Inline entfernt werden.
quelle
Inlined deklarierte virtuelle Funktionen werden beim Aufruf über Objekte inlined und beim Aufruf über Zeiger oder Referenzen ignoriert.
quelle
Mit modernen Compilern schadet es nicht, sie einzuschleusen. Einige alte Compiler / Linker-Kombinationen haben möglicherweise mehrere vtables erstellt, aber ich glaube nicht, dass dies ein Problem mehr ist.
quelle
Ein Compiler kann eine Funktion nur dann inline setzen, wenn der Aufruf zur Kompilierungszeit eindeutig aufgelöst werden kann.
Virtuelle Funktionen werden jedoch zur Laufzeit aufgelöst, sodass der Compiler den Aufruf nicht einbinden kann, da beim Kompilierungstyp der dynamische Typ (und damit die aufzurufende Funktionsimplementierung) nicht bestimmt werden kann.
quelle
In den Fällen, in denen der Funktionsaufruf eindeutig ist und die Funktion ein geeigneter Kandidat für das Inlining ist, ist der Compiler intelligent genug, um den Code trotzdem zu inlineieren.
Der Rest der Zeit "Inline Virtual" ist ein Unsinn, und tatsächlich kompilieren einige Compiler diesen Code nicht.
quelle
Es ist sinnvoll, virtuelle Funktionen zu erstellen und sie dann für Objekte anstatt für Referenzen oder Zeiger aufzurufen. Scott Meyer empfiehlt in seinem Buch "effektives c ++", eine geerbte nicht virtuelle Funktion niemals neu zu definieren. Das ist sinnvoll, denn wenn Sie eine Klasse mit einer nicht virtuellen Funktion erstellen und die Funktion in einer abgeleiteten Klasse neu definieren, können Sie sicher sein, dass Sie sie selbst richtig verwenden, aber Sie können nicht sicher sein, dass andere sie richtig verwenden. Sie können es auch zu einem späteren Zeitpunkt falsch verwenden. Wenn Sie also eine Funktion in einer Basisklasse erstellen und möchten, dass sie erneut definiert werden kann, sollten Sie sie virtuell machen. Wenn es sinnvoll ist, virtuelle Funktionen zu erstellen und sie für Objekte aufzurufen, ist es auch sinnvoll, sie zu integrieren.
quelle
In einigen Fällen kann das Hinzufügen von "Inline" zu einer virtuellen endgültigen Überschreibung dazu führen, dass Ihr Code nicht kompiliert wird, sodass manchmal ein Unterschied besteht (zumindest unter dem VS2017-Compiler)!
Tatsächlich habe ich in VS2017 eine virtuelle Inline-Funktion zum endgültigen Überschreiben ausgeführt und den C ++ 17-Standard zum Kompilieren und Verknüpfen hinzugefügt. Aus irgendeinem Grund ist dies fehlgeschlagen, wenn ich zwei Projekte verwende.
Ich hatte ein Testprojekt und eine Implementierungs-DLL, die ich als Unit-Test durchführe. Im Testprojekt habe ich eine "linker_includes.cpp" -Datei, die die * .cpp-Dateien aus dem anderen Projekt enthält, die benötigt werden. Ich weiß ... Ich weiß, dass ich msbuild für die Verwendung der Objektdateien aus der DLL einrichten kann, aber bitte beachten Sie, dass es sich um eine Microsoft-spezifische Lösung handelt, während das Einschließen der CPP-Dateien nicht mit dem Build-System zusammenhängt und viel einfacher zu versionieren ist eine CPP-Datei als XML-Dateien und Projekteinstellungen und so ...
Interessant war, dass ich ständig Linkerfehler aus dem Testprojekt bekam. Auch wenn ich die Definition der fehlenden Funktionen durch Kopieren, Einfügen und nicht durch Einschließen hinzugefügt habe! So seltsam. Das andere Projekt wurde erstellt und es gibt keine Verbindung zwischen den beiden, außer das Markieren einer Projektreferenz. Es gibt also eine Erstellungsreihenfolge, um sicherzustellen, dass beide immer erstellt werden ...
Ich denke, es ist eine Art Fehler im Compiler. Ich habe keine Ahnung, ob es in dem mit VS2020 gelieferten Compiler vorhanden ist, da ich eine ältere Version verwende, da einige SDKs nur damit ordnungsgemäß funktionieren :-(
Ich wollte nur hinzufügen, dass nicht nur das Markieren als Inline etwas bedeuten kann, sondern unter bestimmten Umständen sogar dazu führen kann, dass Ihr Code nicht erstellt wird! Das ist komisch, aber gut zu wissen.
PS.: Der Code, an dem ich arbeite, bezieht sich auf Computergrafiken, daher bevorzuge ich Inlining. Deshalb habe ich sowohl final als auch inline verwendet. Ich habe den endgültigen Spezifizierer beibehalten, um zu hoffen, dass der Release-Build intelligent genug ist, um die DLL durch Inlining zu erstellen, auch ohne dass ich dies direkt andeutete ...
PS (Linux).: Ich gehe davon aus, dass in gcc oder clang nicht dasselbe passiert, wie ich es routinemäßig getan habe. Ich bin mir nicht sicher, woher dieses Problem kommt ... Ich bevorzuge C ++ unter Linux oder zumindest mit etwas gcc, aber manchmal unterscheidet sich das Projekt in den Anforderungen.
quelle