werden Wenn ich eine Klasse wie folgt definieren:Kann virtuelle Funktionen inlined
class A{
public:
A(){}
virtual ~A(){}
virtual void func(){}
};
es das bedeutet, dass die virtuelle destructor und func
werden Wenn ich eine Klasse wie folgt definieren:Kann virtuelle Funktionen inlined
class A{
public:
A(){}
virtual ~A(){}
virtual void func(){}
};
es das bedeutet, dass die virtuelle destructor und func
inlined sind, ob der Compiler eine Funktion Inline wählt, die definiert ist Inline bleibt dem Compiler überlassen. Im Allgemeinen können virtual
Funktionen nur inline ausgeführt werden, wenn der Compiler entweder beweisen kann, dass der statische Typ dem dynamischen Typ entspricht oder wenn der Compiler den dynamischen Typ sicher bestimmen kann. Wenn Sie beispielsweise einen Wert vom Typ A
verwenden, weiß der Compiler, dass der dynamische Typ nicht unterschiedlich sein kann und die Funktion inline sein kann. Bei Verwendung eines Zeigers oder einer Referenz kann der Compiler im Allgemeinen nicht beweisen, dass der statische Typ derselbe ist und virtual
Funktionen im Allgemeinen dem üblichen virtuellen Versand folgen müssen. Aber selbst wenn ein Zeiger verwendet wird, kann der Compiler genügend Informationen aus dem Kontext haben, um den genauen dynamischen Typ zu kennen. Zum Beispiel, MatthieuM. die folgende exmaple gab:
A* a = new B;
a->func();
In diesem Fall kann der Compiler feststellen, dass a
Punkte zu einem B
Objekt und damit die richtige Version von func()
ohne dynamischen Dispatch nennen. Ohne den dynamischen Versand, func()
könnte dann inline sein. Ob Compiler die entsprechende Analyse durchführen, hängt natürlich von der jeweiligen Implementierung ab.
Wie hvd richtig hingewiesen, kann der virtuelle Versand umgangen werden, indem eine virtuelle Funktion mit voller Qualifikation aufgerufen wird, z. B. a->A::func()
, in welchem Fall die virtuelle Funktion auch inline sein kann. Der Hauptgrund, warum virtuelle Funktionen im Allgemeinen nicht inline sind, ist die Notwendigkeit eines virtuellen Versands. Mit der vollen Qualifikation ist die anzurufende Funktion jedoch bekannt.
Ein nicht virtueller Aufruf einer virtuellen Funktion ('a-> A :: func()') ist ein anderes, etwas offensichtliches Beispiel, bei dem Inlining im Allgemeinen funktioniert. – hvd
Ich sehe den Link @Mat gab, scheint es, dass inlined virtuellen Destruktor sinnvoll ist, aber ich bin immer noch ein wenig verwirrt darüber, wie Destruktoren inline – Ghostblade
* wenn der Compiler kann beweisen, dass der statische Typ entspricht dem dynamischen Typ *: es ist tatsächlich komplizierter als das. Betrachte 'Basis * b = neu abgeleitet {}; b-> func(); ', hier kann der Aufruf inline sein, wenn der Compiler intelligent genug ist, um zu erkennen, dass der dynamische Typ von 'b' notwendigerweise 'Abgeleitet' ist. Clang ist so ein intelligenter Compiler. –
Ja, und in mehrfacher Hinsicht. Sie können einige Beispiele von Devirtualizationin this email Ich schickte an die Clang Mailingliste vor 2 Jahren.
Wie alle Optimierungen, dies die Compiler-Fähigkeiten fehlt noch Alternativen zu beseitigen: Wenn es, dass der virtuelle Anruf dann in Derived::func
behoben ist immer beweisen kann sie es direkt anrufen können.
Es gibt verschiedene Situationen, lassen Sie uns zunächst mit den semantischen Beweise beginnen:
SomeDerived& d
wo SomeDerived
final
zu devirtualization aller Methode erlaubt ist, nenntSomeDerived& d
, wo foo
ist final
ermöglicht auch devirtualization von Dieser bestimmte AnrufDann gibt es Situationen wo Sie den dynamischen Typ des Objekts wissen:
SomeDerived d;
=> der dynamische Typ von d
notwendigerweise SomeDerived
SomeDerived d; Base& b;
=> der dynamische Typ von b
notwendigerweise SomeDerived
Diejenigen 4 devirtualization Situationen werden normalerweise vom Compiler-Frontend gelöst, da sie grundlegendes Wissen über die Sprachsemantik benötigen. Ich kann bestätigen, dass alle 4 in Clang implementiert sind, und ich denke, dass sie auch in gcc implementiert sind.
Allerdings gibt es Situationen, in denen dieses viel abbaut:
struct Base { virtual void foo() = 0; };
struct Derived: Base { virtual void foo() { std::cout << "Hello, World!\n"; };
void opaque(Base& b);
void print(Base& b) { b.foo(); }
int main() {
Derived d;
opaque(d);
print(d);
}
Auch hier, obwohl es offensichtlich ist, dass der Aufruf von foo
zu Derived::foo
behoben ist, Clang/LLVM es nicht optimieren. Das Problem ist, dass:
print(d)
durch ersetzen und den Anrufprint(d)
durch ersetzt sie geht davon aus, dass der virtuelle Zeiger von d
durch opaque
(deren Definition undurchsichtig ist, wie der Name schon sagt) geändert wurden, konnteich habe Bemühungen auf die Clang und LLVM maili gefolgt Als beide Gruppen von Entwicklern über den Verlust von Informationen nachdachten und wie man Clang dazu bringt, LLVM zu sagen: "Es ist in Ordnung", aber leider ist das Problem nicht trivial und wurde noch nicht gelöst ... also die halbherzige Devirtualisierung in das Front-End zu versuchen, alle offensichtlichen Fälle zu bekommen, und einige nicht so offensichtlich (obwohl, per Konvention, das Front-End ist nicht, wo Sie sie implementieren).
Als Referenz kann der Code für die devirtualization in Clang in CGExprCXX.cpp in einer Funktion canDevirtualizeMemberFunctionCalls
genannt finden. Es ist nur ~ 64 Zeilen lang (gerade jetzt) und gründlich kommentiert.
+1 für den Verweis auf den Code. – Surt
Wenn Sie darüber nachdenken, macht das Einfügen virtueller Funktionen keinen Sinn. Der einzige Fall, den ich sehen kann, ist, wenn Sie den Typ zum Zeitpunkt der Kompilierung kennen, aber selbst dann bin ich mir nicht sicher, ob ein Compiler die Optimierung durchführen würde. – Borgleader
http://stackoverflow.com/questions/733737/are-inline-virtual-functions-really-anon-sense?rq=1 – Mat
@Borgleader: Sie tun, wenn sie können.Jedoch ist kein Compiler wirklich gut darin, weil komplexe Regeln in der C++ - Sprache hinsichtlich der Konstruktion und Zerstörung von polymorphen Objekten gelten. Da es im Allgemeinen kein JIT-Verfahren gibt, ist die Untergruppe der Situationen, in denen dies möglich ist, begrenzt. –