webentwicklung-frage-antwort-db.com.de

Sind virtuelle Inline-Funktionen wirklich unsinnig?

Ich habe diese Frage erhalten, als ich einen Kommentar zur Codeüberprüfung erhalten habe, der besagt, dass virtuelle Funktionen nicht inline sein müssen.

Ich dachte, virtuelle Inline-Funktionen könnten sich in Szenarien als nützlich erweisen, in denen Funktionen direkt für Objekte aufgerufen werden. Aber das Gegenargument kam mir in den Sinn: Warum sollte man virtuelle definieren und dann Objekte verwenden, um Methoden aufzurufen?

Verwenden Sie am besten keine virtuellen Inline-Funktionen, da diese sowieso so gut wie 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;
}
163
aJ.

Virtuelle Funktionen können manchmal eingebunden werden. Ein Auszug aus dem ausgezeichneten C++ faq :

"Das einzige Mal, wenn ein virtueller Inline-Aufruf eingebunden werden kann, ist, wenn der Compiler die" genaue Klasse "des Objekts kennt, das das Ziel des virtuellen Funktionsaufrufs ist. Dies kann nur geschehen, wenn der Compiler ein tatsächliches Objekt anstelle eines Zeigers oder hat Verweis auf ein Objekt. Dh entweder mit einem lokalen Objekt, einem globalen/statischen Objekt oder einem vollständig enthaltenen Objekt in einem Verbund. "

142
ya23

C++ 11 hat final hinzugefügt. 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 final deklariert wurde:

class A { 
  virtual void foo();
};
class B : public A {
  inline virtual void foo() final { } 
};
class C : public B
{
};

void bar(B const& b) {
  A const& a = b; // Allowed, every B is an A.
  a.foo(); // Call to B::foo() can be inlined, even if b is actually a class C.
}
70
MSalters

Es gibt eine Kategorie von virtuellen Funktionen, bei denen es immer noch sinnvoll ist, sie inline zu haben. Betrachten Sie den folgenden Fall:

class Base {
public:
  inline virtual ~Base () { }
};

class Derived1 : public Base {
  inline virtual ~Derived1 () { } // Implicitly calls Base::~Base ();
};

class Derived2 : public Derived1 {
  inline virtual ~Derived2 () { } // Implicitly calls Derived1::~Derived1 ();
};

void foo (Base * base) {
  delete base;             // Virtual call
}

Der Aufruf zum Löschen von 'base' führt einen virtuellen Aufruf aus, um den richtigen abgeleiteten Klassendestruktor aufzurufen. Dieser Aufruf ist nicht inline. Da jedoch jeder Destruktor seinen übergeordneten Destruktor aufruft (der in diesen Fällen leer ist), kann der Compiler jene Aufrufe inline ausführen, da sie die Basisklassenfunktionen virtuell nicht aufrufen.

Dasselbe Prinzip gilt für Basisklassenkonstruktoren oder für alle Funktionen, bei denen die abgeleitete Implementierung auch die Basisklassenimplementierung aufruft.

36
Richard Corden

Ich habe Compiler gesehen, die keine V-Tabelle ausgeben, wenn überhaupt keine Nicht-Inline-Funktion vorhanden ist (und die dann in einer Implementierungsdatei anstelle eines Headers definiert wurden). Sie würden Fehler wie missing vtable-for-class-A Oder ähnliches auslösen, und du wärst verdammt verwirrt, wie ich es war.

Dies entspricht zwar nicht dem Standard, es kommt jedoch vor, dass mindestens eine virtuelle Funktion nicht im Header (wenn nur der virtuelle Destruktor) enthalten ist, damit der Compiler an dieser Stelle eine vtable für die Klasse ausgeben kann. Ich weiß, dass es mit einigen Versionen von gcc passiert.

Wie bereits erwähnt, können virtuelle Inline-Funktionen von Vorteil sein manchmal, aber am häufigsten werden Sie sie verwenden, wenn Sie nicht den dynamischen Typ des Objekts kennen war der ganze Grund für virtual an erster Stelle.

Der Compiler kann inline jedoch nicht vollständig ignorieren. Es hat neben der Beschleunigung eines Funktionsaufrufs eine andere Semantik. Das implizite Inline für In-Class-Definitionen ist der Mechanismus, mit dem Sie die Definition in den Header einfügen können: Nur inline -Funktionen können im gesamten Programm mehrmals definiert werden, ohne dass dies einen Verstoß darstellt Regeln. Am Ende verhält es sich so, als hätten Sie es nur einmal im gesamten Programm definiert, obwohl Sie den Header mehrmals in verschiedene miteinander verknüpfte Dateien eingefügt haben.

Nun, eigentlich können virtuelle Funktionen immer inline gesetzt werden , solange sie statisch miteinander verbunden sind: Nehmen wir an, wir haben eine abstrakte Klasse Base mit eine virtuelle Funktion F und abgeleitete Klassen Derived1 und Derived2:

class Base {
  virtual void F() = 0;
};

class Derived1 : public Base {
  virtual void F();
};

class Derived2 : public Base {
  virtual void F();
};

Ein hypotetischer Aufruf b->F(); (mit b vom Typ Base*) Ist offensichtlich virtuell. Aber Sie (oder der Compiler ...) könnten es so umschreiben (angenommen, typeof ist eine typeid -ähnliche Funktion, die einen Wert zurückgibt, der in verwendet werden kann a switch)

switch (typeof(b)) {
  case Derived1: b->Derived1::F(); break; // static, inlineable call
  case Derived2: b->Derived2::F(); break; // static, inlineable call
  case Base:     assert(!"pure virtual function call!");
  default:       b->F(); break; // virtual call (dyn-loaded code)
}

während wir weiterhin RTTI für typeof benötigen, kann der Aufruf effektiv durch Einbetten der vtable in den Anweisungsstrom und Spezialisieren des Aufrufs für alle beteiligten Klassen eingeleitet werden. Dies könnte auch verallgemeinert werden, indem nur einige Klassen spezialisiert werden (sagen wir nur Derived1):

switch (typeof(b)) {
  case Derived1: b->Derived1::F(); break; // hot path
  default:       b->F(); break; // default virtual call, cold path
}
10
CAFxX

inline macht wirklich nichts - es ist ein Hinweis. Der Compiler könnte es ignorieren oder ein Aufrufereignis ohne inline einbinden, wenn er die Implementierung sieht und diese Idee mag. Wenn die Code-Klarheit auf dem Spiel steht, sollte inline entfernt werden.

3
sharptooth

Indem Sie eine virtuelle Methode inline markieren, können Sie die virtuellen Funktionen in den folgenden beiden Fällen weiter optimieren:

3
Chenna Reddy

Inline deklariert Virtuelle Funktionen werden inline dargestellt, wenn sie über Objekte aufgerufen werden, und ignoriert, wenn sie über Zeiger oder Referenzen aufgerufen werden.

3
tarachandverma

Mit modernen Compilern schadet es nicht, sie einzuschleusen. Einige alte Compiler/Linker-Combos haben möglicherweise mehrere vtables erstellt, aber ich glaube nicht, dass dies kein Problem mehr ist.

1
anon

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 inline zu setzen.

Der Rest der Zeit ist "Inline Virtual" ein Unsinn, und in der Tat werden einige Compiler diesen Code nicht kompilieren.

1
moonshadow

Ein Compiler kann eine Funktion nur dann inline setzen, wenn der Aufruf zum Kompilierzeitpunkt eindeutig aufgelöst werden kann.

Virtuelle Funktionen werden jedoch zur Laufzeit aufgelöst, sodass der Compiler den Aufruf nicht inline verarbeiten kann, da beim Kompilierungstyp der dynamische Typ (und damit die aufzurufende Funktionsimplementierung) nicht bestimmt werden kann.

0
PaulJWilliams

Es ist sinnvoll, virtuelle Funktionen zu erstellen und diese dann für Objekte anstelle von Referenzen oder Zeigern aufzurufen. Scott Meyer empfiehlt in seinem Buch "effective c ++", eine vererbte 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 korrekt verwenden, aber Sie können nicht sicher sein, dass andere sie korrekt verwenden. Sie können es auch zu einem späteren Zeitpunkt selbst falsch verwenden. Wenn Sie also eine Funktion in einer Basisklasse erstellen und sie neu definieren möchten, sollten Sie sie virtuell gestalten. Wenn es sinnvoll ist, virtuelle Funktionen zu erstellen und Objekte aufzurufen, ist es auch sinnvoll, sie inline zu setzen.

0
Balthazar