ProgrammierungC++ Entwickler

Was ist eine virtuelle Tabelle (vtable) in C++? Wie implementiert der Compiler dynamische Polymorphie und welche Feinheiten sind mit diesem Mechanismus verbunden?

Bestehen Sie Vorstellungsgespräche mit dem Hintsage-KI-Assistenten

Antwort.

In C++ wird die dynamische Polymorphie durch den Mechanismus der virtuellen Funktionen mithilfe einer speziellen Tabelle — vtable (virtuelle Tabelle) — implementiert. Für jede Klasse mit mindestens einer virtuellen Funktion erstellt der Compiler eine vtable: ein Array von Zeigern auf die virtuellen Funktionen der Klasse. Jedes Objekt einer solchen Klasse speichert einen versteckten Zeiger auf die vtable — den sogenannten vptr (virtueller Zeiger).

Beim Erstellen eines Objekts einer Klasse mit virtuellen Funktionen wird vptr auf die vtable des entsprechenden Objekttyps initialisiert. Beim Aufruf einer virtuellen Funktion erfolgt der Aufruf nicht direkt, sondern über die Adresse, die in der vtable gespeichert ist, wodurch die passende Implementierung basierend auf dem tatsächlichen Typ des Objekts ausgewählt wird (auch wenn über einen Basistyp zugegriffen wird).

Beispiel:

class Base { public: virtual void foo() { std::cout << "Base::foo() "; } }; class Derived : public Base { public: void foo() override { std::cout << "Derived::foo() "; } }; void call_foo(Base* obj) { obj->foo(); // Aufruf über vtable }

Der Aufruf von foo erfolgt über die vtable: wenn es sich um Derived handelt, wird die überschreibende Funktion aufgerufen.

Feinheiten:

  • Wenn eine Klasse keine virtuellen Funktionen enthält, werden vtable und vptr nicht hinzugefügt.
  • Virtuelle Methoden werden nicht inline eingebunden, was oft die Leistung beeinträchtigt.
  • Die Größe des Klassenexemplars wird erhöht (um die Größe von vptr — normalerweise 4/8 Bytes).
  • Virtuelle Aufrufe sind langsamer als direkte Aufrufe.

Trickfrage.

"Ist eine Methode virtuell, wenn sie als virtual deklariert ist, aber in der abgeleiteten Klasse nicht überschrieben wird? Wird in diesem Fall der Aufruf über vtable funktionieren?"

Antwort: Ja, wenn eine Funktion im Basisklasse als virtual deklariert ist, bleibt sie für alle Nachkommen virtuell, auch wenn sie nicht überschrieben wird. Der Aufruf über einen Basispointer erfolgt in jedem Fall über die vtable. Wenn die Methode nicht überschrieben wird, wird die Version aus der Basisklasse aufgerufen.

Beispiel:

struct Base { virtual void foo() { std::cout << "B "; } }; struct Derived : Base { }; Base* obj = new Derived(); obj->foo(); // Gibt "B" aus, aber Aufruf über vtable!

Beispiele für reale Fehler aufgrund fehlenden Wissens über Feinheiten des Themas.


Geschichte

In einem großen Projekt wurde vergessen, den Destruktor als virtuell in der Basisklasse mit virtuellen Funktionen zu deklarieren. Das führte zu Speicherlecks — beim Löschen über einen Basispointer wurde nur der Destruktor der Basisklasse aufgerufen, nicht der des Nachkommens.


Geschichte

In der Klasse wurde eine virtuelle Methode deklariert, die versehentlich im Erben verborgen wurde (nicht override, sondern mit einer anderen Signatur/Argumentnamen). Aufrufe über die Basisklasse führten nicht zur richtigen Funktion, was zu Fehlverhalten führte.


Geschichte

Die Aktualisierung der vtable nach Mehrfachvererbung führte zu Fehlern: falsche Methodenaurufe bei Verwendung von dynamic_cast, da die Verschiebung von vptr in einer komplexen Klassenhierarchie nicht berücksichtigt wurde.