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

Spécifie qu'une fonction membre non statique est virtual et prend en charge la distribution dynamique. Elle ne peut apparaître que dans la decl-specifier-seq de la déclaration initiale d'une fonction membre non statique (c'est-à-dire lorsqu'elle est déclarée dans la définition de la classe).

Table des matières

Explication

Les fonctions virtuelles sont des fonctions membres dont le comportement peut être redéfini dans les classes dérivées. Contrairement aux fonctions non virtuelles, le comportement de redéfinition est préservé même en l'absence d'informations à la compilation sur le type réel de la classe. Autrement dit, si une classe dérivée est manipulée via un pointeur ou une référence vers la classe de base, un appel à une fonction virtuelle redéfinie invoquera le comportement défini dans la classe dérivée. Un tel appel de fonction est appelé appel de fonction virtuelle ou appel virtuel . L'appel de fonction virtuelle est supprimé si la fonction est sélectionnée via une recherche de nom qualifiée (c'est-à-dire si le nom de la fonction apparaît à droite de l'opérateur de résolution de portée :: ).

#include <iostream>
struct Base
{
    virtual void f()
    {
        std::cout << "base\n";
    }
};
struct Derived : Base
{
    void f() override // 'override' est optionnel
    {
        std::cout << "derived\n";
    }
};
int main()
{
    Base b;
    Derived d;
    // appel de fonction virtuelle par référence
    Base& br = b; // le type de br est Base&
    Base& dr = d; // le type de dr est Base& également
    br.f(); // affiche "base"
    dr.f(); // affiche "derived"
    // appel de fonction virtuelle par pointeur
    Base* bp = &b; // le type de bp est Base*
    Base* dp = &d; // le type de dp est Base* également
    bp->f(); // affiche "base"
    dp->f(); // affiche "derived"
    // appel de fonction non virtuelle
    br.Base::f(); // affiche "base"
    dr.Base::f(); // affiche "base"
}

En détail

Si une fonction membre vf est déclarée comme virtual dans une classe Base , et qu'une classe Derived , qui est dérivée, directement ou indirectement, de Base , possède une déclaration pour une fonction membre avec la même

  • nom
  • liste des types de paramètres (mais pas le type de retour)
  • qualificateurs cv
  • qualificateurs de référence

Alors cette fonction dans la classe Derived est également virtual (que le mot-clé virtual soit utilisé ou non dans sa déclaration) et overrides Base::vf (que le spécificateur override soit utilisé ou non dans sa déclaration).

Base::vf n'a pas besoin d'être accessible ou visible pour être redéfini. ( Base::vf peut être déclaré privé, ou Base peut être hérité en utilisant l'héritage privé. Les membres de même nom dans une classe de base de Derived qui hérite Base n'ont pas d'importance pour la détermination de la redéfinition, même s'ils masqueraient Base::vf lors de la recherche de nom.)

class B
{
    virtual void do_f(); // membre privé
public:
    void f() { do_f(); } // interface publique
};
struct D : public B
{
    void do_f() override; // redéfinit B::do_f
};
int main()
{
    D d;
    B* bp = &d;
    bp->f(); // appelle en interne D::do_f();
}

Pour chaque fonction virtuelle, il existe le final overrider , qui est exécuté lorsqu'un appel de fonction virtuelle est effectué. Une fonction membre virtuelle vf d'une classe de base Base est le final overrider sauf si la classe dérivée déclare ou hérite (via l'héritage multiple) une autre fonction qui remplace vf .

struct A { virtual void f(); };     // A::f est virtuelle
struct B : A { void f(); };         // B::f remplace A::f dans B
struct C : virtual B { void f(); }; // C::f remplace A::f dans C
struct D : virtual B {}; // D n'introduit pas de remplacement, B::f est final dans D
struct E : C, D          // E n'introduit pas de remplacement, C::f est final dans E
{
    using A::f; // pas une déclaration de fonction, rend simplement A::f visible pour la recherche
};
int main()
{
    E e;
    e.f();    // appel virtuel appelle C::f, le remplacement final dans e
    e.E::f(); // appel non virtuel appelle A::f, qui est visible dans E
}

Si une fonction a plus d'un final overrider, le programme est mal formé :

struct A
{
    virtual void f();
};
struct VB1 : virtual A
{
    void f(); // remplace A::f
};
struct VB2 : virtual A
{
    void f(); // remplace A::f
};
// struct Error : VB1, VB2
// {
//     // Erreur : A::f a deux remplacements finaux dans Error
// };
struct Okay : VB1, VB2
{
    void f(); // OK : ceci est le remplacement final pour A::f
};
struct VB1a : virtual A {}; // ne déclare pas de remplacement
struct Da : VB1a, VB2
{
    // dans Da, le remplacement final de A::f est VB2::f
};

Une fonction portant le même nom mais avec une liste de paramètres différente ne remplace pas la fonction de base du même nom, mais la masque : lorsque la recherche de nom non qualifiée examine la portée de la classe dérivée, la recherche trouve la déclaration et n'examine pas la classe de base.

struct B
{
    virtual void f();
};
struct D : B
{
    void f(int); // D::f masque B::f (liste de paramètres incorrecte)
};
struct D2 : D
{
    void f(); // D2::f remplace B::f (peu importe qu'elle ne soit pas 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();  // appelle B::f()
    d_as_b.f();  // appelle B::f()
    d2_as_b.f(); // appelle D2::f()
    d_as_d.f();  // Erreur : la recherche dans D ne trouve que f(int)
    d2_as_d.f(); // Erreur : la recherche dans D ne trouve que f(int)
}

Si une fonction est déclarée avec le spécificateur override , mais ne redéfinit pas une fonction virtuelle, le programme est mal formé :

struct B
{
    virtual void f(int);
};
struct D : B
{
    virtual void f(int) override;  // OK, D::f(int) redéfinit B::f(int)
    virtual void f(long) override; // Erreur : f(long) ne redéfinit pas B::f(int)
};

Si une fonction est déclarée avec le spécificateur final , et qu'une autre fonction tente de la redéfinir, le programme est mal formé :

struct B
{
    virtual void f() const final;
};
struct D : B
{
    void f() const; // Erreur : D::f tente de redéfinir B::f déclarée final
};
(depuis C++11)

Les fonctions non membres et les fonctions membres statiques ne peuvent pas être virtuelles.

Les modèles de fonction ne peuvent pas être déclarés virtual . Ceci s'applique uniquement aux fonctions qui sont elles-mêmes des modèles - une fonction membre régulière d'un modèle de classe peut être déclarée virtuelle.

Les fonctions virtuelles (qu'elles soient déclarées virtuelles ou qu'elles en redéfinissent une) ne peuvent avoir aucune contrainte associée.

struct A
{
    virtual void f() requires true; // Error: constrained virtual function
};

Une fonction virtuelle consteval ne doit pas redéfinir ou être redéfinie par une fonction virtuelle non consteval .

(depuis C++20)

Les arguments par défaut pour les fonctions virtuelles sont substitués au moment de la compilation.

Types de retour covariants

Si la fonction Derived::f remplace une fonction Base::f , leurs types de retour doivent être identiques ou être covariants . Deux types sont covariants s'ils satisfont à toutes les exigences suivantes :

  • les deux types sont des pointeurs ou des références (lvalue ou rvalue) vers des classes. Les pointeurs ou références multi-niveaux ne sont pas autorisés.
  • la classe référencée/pointée dans le type de retour de Base::f() doit être une classe de base directe ou indirecte non ambiguë et accessible de la classe référencée/pointée du type de retour de Derived::f() .
  • le type de retour de Derived::f() doit être aussi ou moins cv-qualifié que le type de retour de Base::f() .

La classe dans le type de retour de Derived::f doit être soit Derived elle-même, soit être un type complet au point de déclaration de Derived::f .

Lorsqu'un appel de fonction virtuelle est effectué, le type retourné par le final overrider est implicitement converti vers le type de retour de la fonction surchargée qui a été appelée :

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; // dans Derived, B est une base accessible de D
};
class A; // une classe déclarée à l'avance est un type incomplet
struct Derived : public Base
{
    void vf1();    // virtuel, remplace Base::vf1()
    void vf2(int); // non-virtuel, masque Base::vf2()
//  char vf3();    // Erreur : remplace Base::vf3, mais a un type de retour
                   // différent et non covariant
    D* vf4();      // remplace Base::vf4() et a un type de retour covariant
//  A* vf5();      // Erreur : A est un type incomplet
};
int main()
{
    Derived d;
    Base& br = d;
    Derived& dr = d;
    br.vf1(); // appelle Derived::vf1()
    br.vf2(); // appelle Base::vf2()
//  dr.vf2(); // Erreur : vf2(int) masque vf2()
    B* p = br.vf4(); // appelle Derived::vf4() et convertit le résultat en B*
    D* q = dr.vf4(); // appelle Derived::vf4() et ne convertit pas le résultat en B*
}

Destructeur virtuel

Bien que les destructeurs ne soient pas hérités, si une classe de base déclare son destructeur virtual , le destructeur dérivé le remplace toujours. Cela permet de supprimer des objets alloués dynamiquement de type polymorphe via des pointeurs vers la base.

class Base
{
public:
    virtual ~Base() { /* libère les ressources de Base */ }
};
class Derived : public Base
{
    ~Derived() { /* libère les ressources de Derived */ }
};
int main()
{
    Base* b = new Derived;
    delete b; // Effectue un appel de fonction virtuelle à Base::~Base()
              // puisqu'elle est virtuelle, elle appelle Derived::~Derived() qui peut
              // libérer les ressources de la classe dérivée, puis appelle
              // Base::~Base() suivant l'ordre habituel de destruction
}

De plus, si le destructeur de la classe de base n'est pas virtuel, supprimer un objet de la classe dérivée via un pointeur vers la classe de base est un comportement indéfini , qu'il y ait ou non des ressources qui seraient fuitées si le destructeur dérivé n'est pas invoqué , sauf si la fonction de désallocation sélectionnée est un operator delete destructeur (depuis C++20) .

Une directive utile est que le destructeur de toute classe de base doit être public et virtuel ou protégé et non-virtuel , chaque fois que des expressions delete sont impliquées , par exemple lorsqu'utilisé implicitement dans std::unique_ptr (depuis C++11) .

Pendant la construction et la destruction

Lorsqu'une fonction virtuelle est appelée directement ou indirectement depuis un constructeur ou depuis un destructeur (y compris pendant la construction ou la destruction des membres de données non statiques de la classe, par exemple dans une liste d'initialisation ), et que l'objet auquel l'appel s'applique est l'objet en cours de construction ou de destruction, la fonction appelée est le final overrider dans la classe du constructeur ou du destructeur et non celle qui la remplace dans une classe plus dérivée. En d'autres termes, pendant la construction ou la destruction, les classes plus dérivées n'existent pas.

Lors de la construction d'une classe complexe avec plusieurs branches, dans un constructeur appartenant à une branche, le polymorphisme est restreint à cette classe et ses bases : s'il obtient un pointeur ou une référence vers un sous-objet de base en dehors de cette sous-hiérarchie, et tente d'invoquer un appel de fonction virtuelle (par exemple en utilisant un accès membre explicite), le comportement est indéfini :

struct V
{
    virtual void f();
    virtual void g();
};
struct A : virtual V
{
    virtual void f(); // A::f est le remplacement final de V::f dans A
};
struct B : virtual V
{
    virtual void g(); // B::g est le remplacement final de V::g dans B
    B(V*, A*);
};
struct D : A, B
{
    virtual void f(); // D::f est le remplacement final de V::f dans D
    virtual void g(); // D::g est le remplacement final de V::g dans D
    // note : A est initialisé avant B
    D() : B((A*) this, this) {}
};
// le constructeur de B, appelé depuis le constructeur de D
B::B(V* v, A* a)
{
    f(); // appel virtuel à V::f (bien que D ait le remplacement final, D n'existe pas)
    g(); // appel virtuel à B::g, qui est le remplacement final dans B
    v->g(); // le type de v est V, base de B, l'appel virtuel appelle B::g comme précédemment
    a->f(); // le type de a est A, qui n'est pas une base de B. Il appartient à une branche différente de
            // la hiérarchie. Tenter un appel virtuel via cette branche provoque
            // un comportement indéfini même si A était déjà entièrement construit dans ce
            // cas (il a été construit avant B puisqu'il apparaît avant B dans la liste
            // des bases de D). En pratique, l'appel virtuel à A::f sera
            // tenté en utilisant la table des fonctions membres virtuelles de B, car c'est celle
            // qui est active pendant la construction de B)
}

Mots-clés

virtual

Rapports de défauts

Les rapports de défauts modifiant le comportement suivants ont été appliqués rétroactivement aux normes C++ précédemment publiées.

DR Appliqué à Comportement publié Comportement correct
CWG 258 C++98 une fonction membre non-const d'une classe dérivée pouvait devenir
virtuelle à cause d'une fonction membre virtuelle const de sa base
la virtualité nécessite aussi que les qualifications
cv soient identiques
CWG 477 C++98 une déclaration friend pouvait contenir le virtual spécificateur non autorisé
CWG 1516 C++98 la définition des termes "appel de fonction virtuelle"
et "appel virtuel" n'était pas fournie
fournie

Voir aussi

classes dérivées et modes d'héritage
override spécificateur (C++11) déclare explicitement qu'une méthode remplace une autre méthode
final spécificateur (C++11) déclare qu'une méthode ne peut pas être remplacée ou qu'une classe ne peut pas être dérivée