Maîtriser les fonctions virtuelles en C++ : Concepts, exemples et le mot-clé override
Fonctions virtuelles en C++
Ce tutoriel explique les fonctions virtuelles en C++, leur but et leur utilisation pratique illustrée avec des exemples de code.
Une fonction virtuelle est une fonction membre de classe de base que nous attendons que les classes dérivées redéfinissent. Déclarer une fonction virtuelle assure que l'implémentation la plus dérivée est invoquée, même quand on y accède via un pointeur ou une référence de classe de base.
Quand une classe de base contient un membre non virtuel, l'appel de ce membre via un pointeur de base exécutera toujours la version de base, indépendamment du type d'objet réel. Considérez ce qui suit :
class Base {
public:
void print() {
// implémentation de base
}
};
class Derived : public Base {
public:
void print() {
// implémentation dérivée
}
};
Dans le programme ci-dessous, un Base* pointe vers une instance Derived, pourtant print() se résout à la version de base :
int main() {
Derived derived1;
Base* base1 = &derived1;
base1->print(); // appelle Base::print()
return 0;
}
Pour obtenir un comportement polymorphe, déclarez la fonction comme virtual dans la classe de base :
class Base {
public:
virtual void print() {
// implémentation de base
}
};
Les fonctions virtuelles sont centrales au polymorphisme C++. Pour une analyse plus approfondie, consultez notre guide Polymorphisme en C++.
Exemple 1 : Utilisation basique de fonction virtuelle
#include <iostream>
using namespace std;
class Base {
public:
virtual void print() {
cout << "Fonction de base" << endl;
}
};
class Derived : public Base {
public:
void print() override {
cout << "Fonction dérivée" << endl;
}
};
int main() {
Derived derived1;
Base* base1 = &derived1;
base1->print(); // affiche Fonction dérivée
return 0;
}
Sortie
Fonction dérivée
Puisque Base::print est virtuelle, l'appel distribue vers Derived::print même à travers un pointeur de base.

L'identificateur override en C++
Depuis C++11, le mot-clé override aide à prévenir les redéfinitions accidentelles en forçant le compilateur à vérifier qu'une fonction redéfinit réellement un membre virtuel de base.
Exemple :
class Base {
public:
virtual void print() {
// implémentation de base
}
};
class Derived : public Base {
public:
void print() override {
// implémentation dérivée
}
};
Si vous définissez la fonction de redéfinition en dehors de la classe, utilisez :
class Derived : public Base {
public:
void print() override; // déclaration
};
void Derived::print() {
// implémentation
}
Utilisation sécurisée de override
Sans override, une faute de frappe ou une incompatibilité de signature résulte silencieusement en une nouvelle fonction au lieu d'une redéfinition, menant à des bugs subtils. Le compilateur émettra alors une erreur, rendant le problème immédiatement visible. Pièges courants :
- Noms mal assortis : ex:
pint()au lieu deprint() - Types de retour différents : ex: retourner
intau lieu devoid - Incompatibilité de paramètres : listes de paramètres différentes
- Virtual manquant dans la base : la fonction de base n'est pas virtuelle du tout
Utilisation pratique des fonctions virtuelles
Considérez une classe de base Animal avec des classes dérivées Dog et Cat. Chaque classe contient un membre type initialisé par son constructeur.
class Animal {
private:
string type;
public:
Animal() : type("Animal") {}
virtual string getType() const {
return type;
}
};
class Dog : public Animal {
private:
string type;
public:
Dog() : type("Dog") {}
string getType() const override {
return type;
}
};
class Cat : public Animal {
private:
string type;
public:
Cat() : type("Cat") {}
string getType() const override {
return type;
}
};
Avec getType() virtuelle, un seul helper peut afficher n'importe quel animal :
void print(Animal* ani) {
cout << "Animal : " << ani->getType() << endl;
}
Utilisation dans main() :
int main() {
Animal* animal1 = new Animal();
Animal* dog1 = new Dog();
Animal* cat1 = new Cat();
print(animal1);
print(dog1);
print(cat1);
delete animal1;
delete dog1;
delete cat1;
return 0;
}
Sortie
Animal : Animal Animal : Dog Animal : Cat
En rendant getType() virtuelle, nous évitons de dupliquer la logique print() dans chaque classe dérivée et assurons que le bon type est accédé.
Destructeur virtuel - Point crucial
Quand une classe a des fonctions virtuelles, son destructeur devrait aussi être virtuel pour assurer un nettoyage correct lors de la suppression via un pointeur de base :
class Base {
public:
virtual ~Base() = default; // Destructeur virtuel
};
Langue C
- Comment passer des tableaux aux fonctions en C++ : Guide pratique
- Classes et objets C++ : Guide pratique de la programmation orientée objet
- Fonctions amies et classes amies en C++ : Maîtriser le contrôle d'accès
- Maîtriser les templates de classe en C++ : Guide pratique
- Les Fonctions en C++ Expliquées avec des Exemples de Code Pratiques
- Maîtriser les Fonctions Verilog pour une Conception RTL Efficace
- Maîtriser les fonctions en C : Structure, déclaration et bonnes pratiques
- Comprendre les classes de stockage en C++ : Portée, durée de vie et utilisation
- Maîtriser la surcharge en C++ : Fonctions et opérateurs expliqués