virtual
function specifier
Spezifiziert, dass eine nicht-statische Memberfunktion virtual ist und dynamisches Dispatch unterstützt. Sie darf nur in der decl-specifier-seq der initialen Deklaration einer nicht-statischen Memberfunktion erscheinen (d.h., wenn sie in der Klassendefinition deklariert wird).
Inhaltsverzeichnis |
Erklärung
Virtuelle Funktionen sind Memberfunktionen, deren Verhalten in abgeleiteten Klassen überschrieben werden kann. Im Gegensatz zu nicht-virtuellen Funktionen bleibt das überschriebene Verhalten auch dann erhalten, wenn keine Compilezeit-Informationen über den tatsächlichen Typ der Klasse vorliegen. Das heißt, wenn eine abgeleitete Klasse mittels Zeiger oder Referenz auf die Basisklasse behandelt wird, würde ein Aufruf einer überschriebenen virtuellen Funktion das in der abgeleiteten Klasse definierte Verhalten aufrufen. Ein solcher Funktionsaufruf wird als
virtueller Funktionsaufruf
oder
virtueller Aufruf
bezeichnet. Der virtuelle Funktionsaufruf wird unterdrückt, wenn die Funktion mittels
qualifizierter Namenssuche
ausgewählt wird (das heißt, wenn der Funktionsname rechts vom Bereichsauflösungsoperator
::
erscheint).
#include <iostream> struct Base { virtual void f() { std::cout << "base\n"; } }; struct Derived : Base { void f() override // 'override' ist optional { std::cout << "derived\n"; } }; int main() { Base b; Derived d; // Virtueller Funktionsaufruf über Referenz Base& br = b; // Der Typ von br ist Base& Base& dr = d; // Der Typ von dr ist ebenfalls Base& br.f(); // gibt "base" aus dr.f(); // gibt "derived" aus // Virtueller Funktionsaufruf über Zeiger Base* bp = &b; // Der Typ von bp ist Base* Base* dp = &d; // Der Typ von dp ist ebenfalls Base* bp->f(); // gibt "base" aus dp->f(); // gibt "derived" aus // Nicht-virtueller Funktionsaufruf br.Base::f(); // gibt "base" aus dr.Base::f(); // gibt "base" aus }
Im Detail
Wenn eine Memberfunktion
vf
in einer Klasse
Base
als
virtual
deklariert wird, und eine Klasse
Derived
, die direkt oder indirekt von
Base
abgeleitet ist, eine Deklaration für eine Memberfunktion mit demselben
- Name
- Parameter-Typenliste (jedoch nicht den Rückgabetyp)
- CV-Qualifizierer
- Ref-Qualifizierer
Dann ist diese Funktion in der Klasse
Derived
ebenfalls
virtual
(unabhängig davon, ob das Schlüsselwort
virtual
in ihrer Deklaration verwendet wird) und
überschreibt
Base::vf (unabhängig davon, ob der Spezifizierer
override
in ihrer Deklaration verwendet wird).
Base::vf
muss nicht zugänglich oder sichtbar sein, um überschrieben werden zu können. (
Base::vf
kann als privat deklariert werden, oder
Base
kann mittels privater Vererbung geerbt werden. Alle Member mit demselben Namen in einer Basisklasse von
Derived
, die
Base
erbt, spielen keine Rolle für die Überschreibungsbestimmung, selbst wenn sie
Base::vf
während der Namenssuche verbergen würden.)
class B { virtual void do_f(); // privates Element public: void f() { do_f(); } // öffentliche Schnittstelle }; struct D : public B { void do_f() override; // überschreibt B::do_f }; int main() { D d; B* bp = &d; bp->f(); // ruft intern D::do_f() auf; }
Für jede virtuelle Funktion gibt es den
final overrider
, der ausgeführt wird, wenn ein virtueller Funktionsaufruf erfolgt. Eine virtuelle Memberfunktion
vf
einer Basisklasse
Base
ist der final overrider, es sei denn, die abgeleitete Klasse deklariert oder erbt (durch Mehrfachvererbung) eine andere Funktion, die
vf
überschreibt.
struct A { virtual void f(); }; // A::f ist virtual struct B : A { void f(); }; // B::f überschreibt A::f in B struct C : virtual B { void f(); }; // C::f überschreibt A::f in C struct D : virtual B {}; // D führt keinen Überschreiber ein, B::f ist final in D struct E : C, D // E führt keinen Überschreiber ein, C::f ist final in E { using A::f; // keine Funktionsdeklaration, macht A::f nur für die Suche sichtbar }; int main() { E e; e.f(); // virtueller Aufruf ruft C::f auf, den finalen Überschreiber in e e.E::f(); // nicht-virtueller Aufruf ruft A::f auf, die in E sichtbar ist }
Wenn eine Funktion mehr als einen endgültigen Überschreiber hat, ist das Programm fehlerhaft:
struct A { virtual void f(); }; struct VB1 : virtual A { void f(); // überschreibt A::f }; struct VB2 : virtual A { void f(); // überschreibt A::f }; // struct Error : VB1, VB2 // { // // Fehler: A::f hat zwei finale Überschreiber in Error // }; struct Okay : VB1, VB2 { void f(); // OK: dies ist der finale Überschreiber für A::f }; struct VB1a : virtual A {}; // deklariert keinen Überschreiber struct Da : VB1a, VB2 { // in Da ist der finale Überschreiber von A::f VB2::f };
Eine Funktion mit demselben Namen aber unterschiedlicher Parameterliste überschreibt die Basis-Funktion desselben Namens nicht, sondern versteckt sie: wenn unqualified name lookup den Geltungsbereich der abgeleiteten Klasse untersucht, findet die Suche die Deklaration und untersucht nicht die Basisklasse.
struct B { virtual void f(); }; struct D : B { void f(int); // D::f versteckt B::f (falsche Parameterliste) }; struct D2 : D { void f(); // D2::f überschreibt B::f (spielt keine Rolle, dass es nicht sichtbar ist) }; int main() { B b; B& b_as_b = b; D d; B& d_as_b = d; D& d_as_d = d; D2 d2; B& d2_as_b = d2; D& d2_as_d = d2; b_as_b.f(); // ruft B::f() auf d_as_b.f(); // ruft B::f() auf d2_as_b.f(); // ruft D2::f() auf d_as_d.f(); // Fehler: Suche in D findet nur f(int) d2_as_d.f(); // Fehler: Suche in D findet nur f(int) }
|
Wenn eine Funktion mit dem Spezifizierer
struct B { virtual void f(int); }; struct D : B { virtual void f(int) override; // OK, D::f(int) überschreibt B::f(int) virtual void f(long) override; // Fehler: f(long) überschreibt nicht B::f(int) };
Wenn eine Funktion mit dem Spezifizierer
struct B { virtual void f() const final; }; struct D : B { void f() const; // Fehler: D::f versucht final B::f zu überschreiben }; |
(seit C++11) |
Nicht-Member-Funktionen und statische Member-Funktionen können nicht virtuell sein.
Funktions-Templates können nicht als
virtual
deklariert werden. Dies gilt nur für Funktionen, die selbst Templates sind - eine reguläre Member-Funktion eines Klassen-Templates kann als virtual deklariert werden.
|
Virtuelle Funktionen (ob als virtual deklariert oder eine überschreibend) dürfen keine assoziierten Constraints haben. struct A { virtual void f() requires true; // Fehler: eingeschränkte virtuelle Funktion };
Eine
|
(seit C++20) |
Default-Argumente für virtuelle Funktionen werden zur Kompilierzeit substituiert.
Kovariante Rückgabetypen
Wenn die Funktion
Derived::f
eine Funktion
Base::f
überschreibt, müssen ihre Rückgabetypen entweder identisch sein oder
kovariant
sein. Zwei Typen sind kovariant, wenn sie alle folgenden Anforderungen erfüllen:
- beide Typen sind Zeiger oder Referenzen (lvalue oder rvalue) auf Klassen. Mehrstufige Zeiger oder Referenzen sind nicht erlaubt.
-
die referenzierte/gezeigte Klasse im Rückgabetyp von
Base::f()muss eine eindeutige und zugängliche direkte oder indirekte Basisklasse der referenzierten/gezeigten Klasse des Rückgabetyps vonDerived::f()sein. -
der Rückgabetyp von
Derived::f()muss gleich oder weniger cv-qualifiziert sein als der Rückgabetyp vonBase::f().
Die Klasse im Rückgabetyp von
Derived::f
muss entweder
Derived
selbst sein, oder muss ein
vollständiger Typ
zum Zeitpunkt der Deklaration von
Derived::f
sein.
Wenn ein Aufruf einer virtuellen Funktion erfolgt, wird der vom endgültigen Überschreiber zurückgegebene Typ implizit konvertiert in den Rückgabetyp der aufgerufenen überschriebenen Funktion:
class B {}; struct Base { virtual void vf1(); virtual void vf2(); virtual void vf3(); virtual B* vf4(); virtual B* vf5(); }; class D : private B { friend struct Derived; // in Derived, B ist eine zugreifbare Basisklasse von D }; class A; // vorwärtsdeklarierte Klasse ist ein unvollständiger Typ struct Derived : public Base { void vf1(); // virtuell, überschreibt Base::vf1() void vf2(int); // nicht virtuell, versteckt Base::vf2() // char vf3(); // Fehler: überschreibt Base::vf3, hat aber unterschiedlichen // und nicht kovarianten Rückgabetyp D* vf4(); // überschreibt Base::vf4() und hat kovarianten Rückgabetyp // A* vf5(); // Fehler: A ist unvollständiger Typ }; int main() { Derived d; Base& br = d; Derived& dr = d; br.vf1(); // ruft Derived::vf1() auf br.vf2(); // ruft Base::vf2() auf // dr.vf2(); // Fehler: vf2(int) versteckt vf2() B* p = br.vf4(); // ruft Derived::vf4() auf und konvertiert das Ergebnis zu B* D* q = dr.vf4(); // ruft Derived::vf4() auf und konvertiert das Ergebnis nicht zu B* }
Virtueller Destruktor
Obwohl Destruktoren nicht vererbt werden, wenn eine Basisklasse ihren Destruktor als
virtual
deklariert, überschreibt der abgeleitete Destruktor ihn immer. Dies ermöglicht es, dynamisch allozierte Objekte polymorphen Typs durch Zeiger auf die Basisklasse zu löschen.
class Base { public: virtual ~Base() { /* gibt Ressourcen von Base frei */ } }; class Derived : public Base { ~Derived() { /* gibt Ressourcen von Derived frei */ } }; int main() { Base* b = new Derived; delete b; // Führt einen virtuellen Funktionsaufruf zu Base::~Base() durch // da es virtuell ist, ruft es Derived::~Derived() auf, welches // Ressourcen der abgeleiteten Klasse freigeben kann, und ruft dann // Base::~Base() gemäß der üblichen Reihenfolge der Zerstörung auf }
Darüber hinaus ist das Löschen eines abgeleiteten Klassenobjekts über einen Zeiger auf die Basisklasse undefiniertes Verhalten , wenn der Destruktor der Basisklasse nicht virtuell ist, unabhängig davon, ob Ressourcen verloren gehen würden, wenn der abgeleitete Destruktor nicht aufgerufen wird , es sei denn, die ausgewählte Freigabefunktion ist ein zerstörender operator delete (seit C++20) .
Eine nützliche Richtlinie ist, dass der Destruktor jeder Basisklasse public und virtual oder protected und non-virtual sein muss, sobald delete-Ausdrücke involviert sind , z.B. wenn implizit verwendet in std::unique_ptr (since C++11) .
Während Konstruktion und Destruktion
Wenn eine virtuelle Funktion direkt oder indirekt aus einem Konstruktor oder Destruktor aufgerufen wird (einschließlich während der Konstruktion oder Destruktion der nicht-statischen Datenelemente der Klasse, z.B. in einer initializer list ), und das Objekt, auf das der Aufruf angewendet wird, das sich im Konstruktions- oder Destruktionsprozess befindende Objekt ist, dann ist die aufgerufene Funktion der final overrider in der Klasse des Konstruktors oder Destruktors und nicht eine überschreibende Funktion in einer abgeleiteteren Klasse. Mit anderen Worten: Während der Konstruktion oder Destruktion existieren die abgeleiteteren Klassen nicht.
Beim Konstruieren einer komplexen Klasse mit mehreren Verzweigungen ist innerhalb eines Konstruktors, der zu einem Zweig gehört, die Polymorphie auf diese Klasse und ihre Basisklassen beschränkt: Wenn es einen Zeiger oder eine Referenz auf ein Basis-Subobjekt außerhalb dieser Subhierarchie erhält und versucht, einen virtuellen Funktionsaufruf durchzuführen (z.B. durch expliziten Member-Zugriff), ist das Verhalten undefiniert:
struct V { virtual void f(); virtual void g(); }; struct A : virtual V { virtual void f(); // A::f ist der finale Überschreiber von V::f in A }; struct B : virtual V { virtual void g(); // B::g ist der finale Überschreiber von V::g in B B(V*, A*); }; struct D : A, B { virtual void f(); // D::f ist der finale Überschreiber von V::f in D virtual void g(); // D::g ist der finale Überschreiber von V::g in D // Hinweis: A wird vor B initialisiert D() : B((A*) this, this) {} }; // Der Konstruktor von B, aufgerufen aus dem Konstruktor von D B::B(V* v, A* a) { f(); // Virtueller Aufruf von V::f (obwohl D den finalen Überschreiber hat, existiert D nicht) g(); // Virtueller Aufruf von B::g, welcher der finale Überschreiber in B ist v->g(); // v's Typ V ist Basis von B, virtueller Aufruf ruft B::g wie zuvor auf a->f(); // a's Typ A ist keine Basis von B. Es gehört zu einem anderen Zweig der // Hierarchie. Der Versuch eines virtuellen Aufrufs durch diesen Zweig verursacht // undefiniertes Verhalten, selbst wenn A in diesem Fall bereits vollständig // konstruiert wurde (es wurde vor B konstruiert, da es in der Liste // der Basen von D vor B erscheint). In der Praxis wird der virtuelle Aufruf // von A::f unter Verwendung der virtuellen Memberfunktionstabelle von B // versucht, da diese während der Konstruktion von B aktiv ist) }
Schlüsselwörter
Fehlerberichte
Die folgenden verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
| DR | Angewendet auf | Verhalten wie veröffentlicht | Korrektes Verhalten |
|---|---|---|---|
| CWG 258 | C++98 |
eine nicht-konstante Memberfunktion einer abgeleiteten Klasse könnte
virtuell werden aufgrund einer konstanten virtuellen Memberfunktion ihrer Basis |
Virtualität erfordert ebenfalls gleiche
CV-Qualifikationen |
| CWG 477 | C++98 | eine Friend-Deklaration konnte das virtual Spezifizierer enthalten | nicht erlaubt |
| CWG 1516 | C++98 |
die Definition der Begriffe "virtueller Funktionsaufruf"
und "virtueller Aufruf" waren nicht bereitgestellt |
bereitgestellt |
Siehe auch
| abgeleitete Klassen und Vererbungsmodi | |
override
Spezifizierer
(C++11)
|
deklariert explizit, dass eine Methode eine andere Methode überschreibt |
final
Spezifizierer
(C++11)
|
deklariert, dass eine Methode nicht überschrieben oder eine Klasse nicht abgeleitet werden kann |