Recherche…


Syntaxe

  • vide virtuel f ();

  • void virtuel g () = 0;

  • // C ++ 11 ou version ultérieure:

    • virtual void h () remplace;
    • annuler i () outrepasser;
    • vide virtuel j () final;
    • void k () final;

Remarques

Utilisation de la substitution avec virtual en C ++ 11 et versions ultérieures

Le override spécificateur a une signification particulière à partir de C ++ 11, s'il est ajouté à la signature de fin de fonction. Cela signifie qu'une fonction est

  • Remplacement de la fonction présente dans la classe de base &
  • La fonction de classe de base est virtual

Il n'y a pas de signification à l' run time de ce spécificateur car il est principalement utilisé comme une indication pour les compilateurs

L'exemple ci-dessous montrera le changement de comportement avec notre sans utiliser de remplacement.

Sans override :

#include <iostream>

struct X {
    virtual void f() { std::cout << "X::f()\n"; }
};

struct Y : X {
    // Y::f() will not override X::f() because it has a different signature,
    // but the compiler will accept the code (and silently ignore Y::f()).
    virtual void f(int a) { std::cout << a << "\n"; }
};

Avec override :

#include <iostream>

struct X {
    virtual void f() { std::cout << "X::f()\n"; }
};

struct Y : X {
    // The compiler will alert you to the fact that Y::f() does not
    // actually override anything.
    virtual void f(int a) override { std::cout << a << "\n"; }
};

Notez que le override n'est pas un mot-clé, mais un identifiant spécial qui peut uniquement apparaître dans les signatures de fonction. Dans tous les autres contextes, la override peut toujours être utilisée comme identifiant:

void foo() {
    int override = 1; // OK.
    int virtual = 2;  // Compilation error: keywords can't be used as identifiers.
}

Fonctions membres virtuelles et non virtuelles

Avec des fonctions membres virtuelles:

#include <iostream>

struct X {
    virtual void f() { std::cout << "X::f()\n"; }
};

struct Y : X {
    // Specifying virtual again here is optional
    // because it can be inferred from X::f().
    virtual void f() { std::cout << "Y::f()\n"; } 
};

void call(X& a) {
    a.f();
}

int main() {
    X x;
    Y y;
    call(x); // outputs "X::f()"
    call(y); // outputs "Y::f()"
}

Sans fonctions membres virtuelles:

#include <iostream>

struct X {
   void f() { std::cout << "X::f()\n"; }
};

struct Y : X {
   void f() { std::cout << "Y::f()\n"; }
};

void call(X& a) {
    a.f();
}

int main() {
    X x;
    Y y;
    call(x); // outputs "X::f()"
    call(y); // outputs "X::f()"
}

Fonctions virtuelles finales

C ++ 11 introduit un spécificateur final qui interdit la méthode si elle apparaît dans la signature de la méthode:

class Base {
public:
    virtual void foo() {
        std::cout << "Base::Foo\n";
    }
};

class Derived1 : public Base {
public:
    // Overriding Base::foo
    void foo() final {
        std::cout << "Derived1::Foo\n";
    }
};

class Derived2 : public Derived1 {
public:
    // Compilation error: cannot override final method
    virtual void foo() {
        std::cout << "Derived2::Foo\n";
    }
};

Le spécificateur final ne peut être utilisé qu'avec la fonction membre «virtual» et ne peut pas être appliqué aux fonctions membres non virtuelles.

Comme final , il existe également un appel de spécificateur «override» qui empêche la substitution des fonctions virtual dans la classe dérivée.

Les spécificateurs override et final peuvent être combinés pour avoir l'effet désiré:

class Derived1 : public Base {
public:
    void foo() final override {
        std::cout << "Derived1::Foo\n";
    }
};

Comportement des fonctions virtuelles dans les constructeurs et les destructeurs

Le comportement des fonctions virtuelles dans les constructeurs et les destructeurs est souvent déroutant au premier contact.

#include <iostream>
using namespace std;

class base { 
public:
    base() { f("base constructor"); }
    ~base() { f("base destructor"); }

    virtual const char* v() { return "base::v()"; }

    void f(const char* caller) { 
        cout << "When called from " << caller << ", "  << v() << " gets called.\n"; 
    }        
};

class derived : public base {
public:
    derived() { f("derived constructor"); }
    ~derived() { f("derived destructor"); }

    const char* v() override { return "derived::v()"; }

};

int main() {
     derived d;
}

Sortie:

Appelé par le constructeur de base, base :: v () est appelé.
Lorsqu'elle est appelée à partir d'un constructeur dérivé, :: v () dérivé est appelé.
Lorsqu'elle est appelée depuis un destructeur dérivé, :: v () dérivé est appelé.
Appelé à partir du destructeur de base, base :: v () est appelé.

La raison en est que la classe dérivée peut définir des membres supplémentaires qui ne sont pas encore initialisés (dans le cas du constructeur) ou déjà détruits (dans le cas du destructeur), et appeler ses fonctions membres serait dangereux. Par conséquent, lors de la construction et de la destruction d'objets C ++, le type dynamique de *this est considéré comme étant la classe du constructeur ou du destructeur et non une classe plus dérivée.

Exemple:

#include <iostream>
#include <memory>

using namespace std;
class base {
public:
    base()
    {
        std::cout << "foo is " << foo() << std::endl;
    }
    virtual int foo() { return 42; }
};

class derived : public base {
    unique_ptr<int> ptr_;
public:
    derived(int i) : ptr_(new int(i*i)) { }
    // The following cannot be called before derived::derived due to how C++ behaves, 
    // if it was possible... Kaboom!
    int foo() override   { return *ptr_; } 
};

int main() {
    derived d(4);
}

Fonctions virtuelles pures

Nous pouvons également spécifier qu'une fonction virtual est pure virtuelle (abstraite), en ajoutant = 0 à la déclaration. Les classes avec une ou plusieurs fonctions virtuelles pures sont considérées comme abstraites et ne peuvent pas être instanciées; seules les classes dérivées qui définissent ou héritent des définitions pour toutes les fonctions virtuelles pures peuvent être instanciées.

struct Abstract {
    virtual void f() = 0;
};

struct Concrete {
    void f() override {}
};

Abstract a; // Error.
Concrete c; // Good.

Même si une fonction est spécifiée comme pure virtuelle, une implémentation par défaut peut lui être attribuée. Malgré cela, la fonction sera toujours considérée comme abstraite et les classes dérivées devront la définir avant de pouvoir être instanciées. Dans ce cas, la version dérivée de la fonction est même autorisée à appeler la version de la classe de base.

struct DefaultAbstract {
    virtual void f() = 0;
};
void DefaultAbstract::f() {}

struct WhyWouldWeDoThis : DefaultAbstract {
    void f() override { DefaultAbstract::f(); }
};

Il y a plusieurs raisons pour lesquelles nous pourrions vouloir faire ceci:

  • Si nous voulons créer une classe qui ne peut pas être elle-même instanciée, mais n'empêche pas ses classes dérivées d'être instanciées, nous pouvons déclarer le destructeur comme virtuel pur. En tant que destructeur, il doit être défini de toute façon si nous voulons pouvoir désallouer l'instance. Et comme le destructeur est probablement déjà virtuel pour empêcher les fuites de mémoire lors d'une utilisation polymorphe , nous ne rencontrerons pas de problèmes de performances inutiles en déclarant une autre fonction virtual . Cela peut être utile lors de la création d'interfaces.

      struct Interface {
          virtual ~Interface() = 0;
      };
      Interface::~Interface() = default;
    
      struct Implementation : Interface {};
      // ~Implementation() is automatically defined by the compiler if not explicitly
      //  specified, meeting the "must be defined before instantiation" requirement.
    
  • Si la plupart ou la totalité des implémentations de la fonction virtuelle pure contiennent du code en double, ce code peut plutôt être déplacé vers la version de classe de base, ce qui facilite la gestion du code.

      class SharedBase {
          State my_state;
          std::unique_ptr<Helper> my_helper;
          // ...
    
        public:
          virtual void config(const Context& cont) = 0;
          // ...
      };
      /* virtual */ void SharedBase::config(const Context& cont) {
          my_helper = new Helper(my_state, cont.relevant_field);
          do_this();
          and_that();
      }
    
      class OneImplementation : public SharedBase {
          int i;
          // ...
    
        public:
          void config(const Context& cont) override;
          // ...
      };
      void OneImplementation::config(const Context& cont) /* override */ {
          my_state = { cont.some_field, cont.another_field, i };
          SharedBase::config(cont);
          my_unique_setup();
      };
    
      // And so on, for other classes derived from SharedBase.
    


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow