virtual Funktionsspezifizierer
Der virtual-Spezifizierer gibt an, dass eine nicht-statische Member-Funktion virtuell ist und dynamische Dispatching unterstützt. Er kann nur in der decl-specifier-seq der anfänglichen Deklaration einer nicht-statischen Member-Funktion erscheinen (d.h. wenn sie in der Klassendefinition deklariert wird).
Inhalt |
[edit] Erklärung
Virtuelle Funktionen sind Member-Funktionen, deren Verhalten in abgeleiteten Klassen überschrieben werden kann. Im Gegensatz zu nicht-virtuellen Funktionen bleibt das überschreibende Verhalten erhalten, auch wenn keine Kompilierzeitinformationen über den tatsächlichen Typ der Klasse vorliegen. Das heißt, wenn eine abgeleitete Klasse über einen Zeiger oder eine 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 über qualifizierte Namensauflösung ausgewählt wird (d.h. wenn der Name der Funktion rechts vom Scope-Auflösungsoperator :: erscheint).
#include <iostream> struct Base { virtual void f() { std::cout << "base\n"; } }; struct Derived : Base { void f() override // 'override' is optional { std::cout << "derived\n"; } }; int main() { Base b; Derived d; // virtual function call through reference Base& br = b; // the type of br is Base& Base& dr = d; // the type of dr is Base& as well br.f(); // prints "base" dr.f(); // prints "derived" // virtual function call through pointer Base* bp = &b; // the type of bp is Base* Base* dp = &d; // the type of dp is Base* as well bp->f(); // prints "base" dp->f(); // prints "derived" // non-virtual function call br.Base::f(); // prints "base" dr.Base::f(); // prints "base" }
[edit] Im Detail
Wenn eine Member-Funktion vf in einer Klasse Base als virtual deklariert ist und eine Klasse Derived, die direkt oder indirekt von Base abgeleitet ist, eine Deklaration für eine Member-Funktion mit der gleichen
- name
- Parameter-Typ-Liste (aber nicht dem Rückgabetyp)
- cv-Qualifizierer
- Referenz-Qualifizierer
hat, dann ist diese Funktion in der Klasse Derived ebenfalls virtuell (unabhängig davon, ob das Schlüsselwort virtual in ihrer Deklaration verwendet wird) und überschreibt Base::vf (unabhängig davon, ob das Wort override in ihrer Deklaration verwendet wird).
Base::vf muss nicht zugänglich oder sichtbar sein, um überschrieben zu werden. (Base::vf kann als privat deklariert werden, oder Base kann über private Vererbung vererbt werden. Beliebige Member mit dem gleichen Namen in einer Basisklasse von Derived, die Base erbt, spielen für die Ermittlung der Überschreibung keine Rolle, selbst wenn sie Base::vf während der Namensauflösung verstecken würden.)
class B { virtual void do_f(); // private member public: void f() { do_f(); } // public interface }; struct D : public B { void do_f() override; // overrides B::do_f }; int main() { D d; B* bp = &d; bp->f(); // internally calls D::do_f(); }
Für jede virtuelle Funktion gibt es den endgültigen Überschreiber, der ausgeführt wird, wenn ein virtueller Funktionsaufruf erfolgt. Eine virtuelle Member-Funktion vf einer Basisklasse Base ist der endgültige Überschreiber, es sei denn, die abgeleitete Klasse deklariert oder erbt (durch Mehrfachvererbung) eine andere Funktion, die vf überschreibt.
struct A { virtual void f(); }; // A::f is virtual struct B : A { void f(); }; // B::f overrides A::f in B struct C : virtual B { void f(); }; // C::f overrides A::f in C struct D : virtual B {}; // D does not introduce an overrider, B::f is final in D struct E : C, D // E does not introduce an overrider, C::f is final in E { using A::f; // not a function declaration, just makes A::f visible to lookup }; int main() { E e; e.f(); // virtual call calls C::f, the final overrider in e e.E::f(); // non-virtual call calls A::f, which is visible in E }
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(); // overrides A::f }; struct VB2 : virtual A { void f(); // overrides A::f }; // struct Error : VB1, VB2 // { // // Error: A::f has two final overriders in Error // }; struct Okay : VB1, VB2 { void f(); // OK: this is the final overrider for A::f }; struct VB1a : virtual A {}; // does not declare an overrider struct Da : VB1a, VB2 { // in Da, the final overrider of A::f is VB2::f };
Eine Funktion mit dem gleichen Namen, aber einer anderen Parameterliste überschreibt die Basis-Funktion mit dem gleichen Namen nicht, sondern versteckt sie: Wenn die unqualifizierte Namensauflösung den Gültigkeitsbereich der abgeleiteten Klasse untersucht, findet die Auflösung die Deklaration und untersucht nicht die Basisklasse.
struct B { virtual void f(); }; struct D : B { void f(int); // D::f hides B::f (wrong parameter list) }; struct D2 : D { void f(); // D2::f overrides B::f (doesn't matter that it's not visible) }; 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(); // calls B::f() d_as_b.f(); // calls B::f() d2_as_b.f(); // calls D2::f() d_as_d.f(); // Error: lookup in D finds only f(int) d2_as_d.f(); // Error: lookup in D finds only 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) overrides B::f(int) virtual void f(long) override; // Error: f(long) does not override B::f(int) }; Wenn eine Funktion mit dem Spezifizierer struct B { virtual void f() const final; }; struct D : B { void f() const; // Error: D::f attempts to override final B::f }; |
(seit C++11) |
Nicht-Member-Funktionen und statische Member-Funktionen können nicht virtuell sein.
Funktionstemplates können nicht als virtual deklariert werden. Dies gilt nur für Funktionen, die selbst Templates sind – eine reguläre Member-Funktion einer Klassenvorlage kann als virtuell deklariert werden.
|
Virtuelle Funktionen (egal ob als virtuell deklariert oder eine überschreibende) dürfen keine damit verbundenen Einschränkungen haben. struct A { virtual void f() requires true; // Error: constrained virtual function }; Eine |
(seit C++20) |
Standardargumente für virtuelle Funktionen werden zur Kompilierzeit substituiert.
[edit] Kovariante Rückgabetypen
Wenn die Funktion Derived::f eine Funktion Base::f überschreibt, müssen ihre Rückgabetypen entweder gleich 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. Mehrfache Zeiger oder Referenzen sind nicht erlaubt.
- Die referenzierte/zeigt-auf-Klasse im Rückgabetyp von
Base::f()muss eine eindeutige und zugängliche direkte oder indirekte Basisklasse der referenzierten/zeigt-auf-Klasse des Rückgabetyps vonDerived::f()sein. - Der Rückgabetyp von
Derived::f()muss genauso 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 ein vollständiger Typ zum Zeitpunkt der Deklaration von Derived::f sein.
Wenn ein virtueller Funktionsaufruf erfolgt, wird der vom endgültigen Überschreiber zurückgegebene Typ in den Rückgabetyp der aufgerufenen überschriebenen Funktion implizit konvertiert.
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 is an accessible base of D }; class A; // forward-declared class is an incomplete type struct Derived : public Base { void vf1(); // virtual, overrides Base::vf1() void vf2(int); // non-virtual, hides Base::vf2() // char vf3(); // Error: overrides Base::vf3, but has different // and non-covariant return type D* vf4(); // overrides Base::vf4() and has covariant return type // A* vf5(); // Error: A is incomplete type }; int main() { Derived d; Base& br = d; Derived& dr = d; br.vf1(); // calls Derived::vf1() br.vf2(); // calls Base::vf2() // dr.vf2(); // Error: vf2(int) hides vf2() B* p = br.vf4(); // calls Derived::vf4() and converts the result to B* D* q = dr.vf4(); // calls Derived::vf4() and does not convert the result to B* }
[edit] Virtueller Destruktor
Obwohl Destruktoren nicht vererbt werden, überschreibt der abgeleitete Destruktor immer den Basisklassen-Destruktor, wenn dieser als virtual deklariert ist. Dies ermöglicht das Löschen von dynamisch allozierten Objekten polymorpher Typen über Zeiger auf die Basisklasse.
class Base { public: virtual ~Base() { /* releases Base's resources */ } }; class Derived : public Base { ~Derived() { /* releases Derived's resources */ } }; int main() { Base* b = new Derived; delete b; // Makes a virtual function call to Base::~Base() // since it is virtual, it calls Derived::~Derived() which can // release resources of the derived class, and then calls // Base::~Base() following the usual order of destruction }
Darüber hinaus ist das Löschen eines abgeleiteten Klassenobjekts über einen Zeiger auf die Basisklasse undefiniertes Verhalten, wenn der Basisklassen-Destruktor nicht virtuell ist, unabhängig davon, ob Ressourcen verleert würden, wenn der abgeleitete Destruktor nicht aufgerufen wird, es sei denn, die ausgewählte Deallokationsfunktion ist ein zerstörender operator delete(seit C++20).
Eine nützliche Richtlinie ist, dass der Destruktor jeder Basisklasse öffentlich und virtuell oder geschützt und nicht-virtuell sein sollte, wenn Löschoperationen beteiligt sind, z.B. wenn sie implizit in std::unique_ptr verwendet werden(seit C++11).
[edit] Während Konstruktion und Zerstörung
Wenn eine virtuelle Funktion direkt oder indirekt von einem Konstruktor oder Destruktor aufgerufen wird (einschließlich während der Konstruktion oder Zerstörung der nicht-statischen Datenmember der Klasse, z.B. in einer Member-Initialisierungsliste), und das Objekt, auf das sich der Aufruf bezieht, das zu konstruierende oder zu zerstörende Objekt ist, dann wird die aufgerufene Funktion der endgültige Überschreiber in der Klasse des Konstruktors oder Destruktors und nicht einer, der sie in einer weiter abgeleiteten Klasse überschreibt. Mit anderen Worten, während der Konstruktion oder Zerstörung existieren die weiter abgeleiteten Klassen nicht.
Beim Konstruieren einer komplexen Klasse mit mehreren Zweigen ist die Polymorphie innerhalb eines Konstruktors, der zu einem Zweig gehört, auf diese Klasse und ihre Basen beschränkt: Wenn er einen Zeiger oder eine Referenz auf ein Basis-Subobjekt außerhalb dieser Subhierarchie erhält und versucht, einen virtuellen Funktionsaufruf auszuführen (z.B. mittels expliziter Member-Zugriff), ist das Verhalten undefiniert.
struct V { virtual void f(); virtual void g(); }; struct A : virtual V { virtual void f(); // A::f is the final overrider of V::f in A }; struct B : virtual V { virtual void g(); // B::g is the final overrider of V::g in B B(V*, A*); }; struct D : A, B { virtual void f(); // D::f is the final overrider of V::f in D virtual void g(); // D::g is the final overrider of V::g in D // note: A is initialized before B D() : B((A*) this, this) {} }; // the constructor of B, called from the constructor of D B::B(V* v, A* a) { f(); // virtual call to V::f (although D has the final overrider, D doesn't exist) g(); // virtual call to B::g, which is the final overrider in B v->g(); // v's type V is base of B, virtual call calls B::g as before a->f(); // a’s type A is not a base of B. it belongs to a different branch of the // hierarchy. Attempting a virtual call through that branch causes // undefined behavior even though A was already fully constructed in this // case (it was constructed before B since it appears before B in the list // of the bases of D). In practice, the virtual call to A::f will be // attempted using B's virtual member function table, since that's what // is active during B's construction) }
[edit] Schlüsselwörter
[edit] Fehlerberichte
Die folgenden Verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
| DR | angewendet auf | Verhalten wie veröffentlicht | Korrigiertes Verhalten |
|---|---|---|---|
| CWG 258 | C++98 | eine nicht-const Member-Funktion einer abgeleiteten Klasse könnte virtuell werden wegen einer const virtuellen Member-Funktion ihrer Basis |
Virtualität erfordert auch cv- Qualifizierer gleich |
| CWG 477 | C++98 | eine friend-Deklaration könnte den virtual Spezifizierer enthalten. | nicht erlaubt |
| CWG 1516 | C++98 | die Definition der Begriffe "virtueller Funktionsaufruf" und "virtueller Aufruf" wurden nicht bereitgestellt. |
bereitgestellt |
[edit] 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 werden kann oder eine Klasse nicht von ihr abgeleitet werden kann |