Namespaces
Variants

virtual function specifier

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Virtual function
override specifier (C++11)
final specifier (C++11)
Special member functions
Templates
Miscellaneous

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 override deklariert wird, aber keine virtuelle Funktion überschreibt, ist das Programm fehlerhaft:

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 final deklariert wird und eine andere Funktion versucht, sie zu überschreiben, ist das Programm fehlerhaft:

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 consteval virtuelle Funktion darf keine nicht- consteval virtuelle Funktion überschreiben oder von einer solchen überschrieben werden.

(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 von Derived::f() sein.
  • der Rückgabetyp von Derived::f() muss gleich oder weniger cv-qualifiziert sein als der Rückgabetyp von Base::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

virtual

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