Blog ENI : Toute la veille numérique !
🐠 -25€ dès 75€ 
+ 7 jours d'accès à la Bibliothèque Numérique ENI. Cliquez ici
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici
  1. Livres et vidéos
  2. Langage C++
  3. Polymorphisme et virtualité
Extrait - Langage C++ De l'héritage C au C++ moderne (avec programmes d'illustration) (2e édition)
Extraits du livre
Langage C++ De l'héritage C au C++ moderne (avec programmes d'illustration) (2e édition) Revenir à la page d'achat du livre

Polymorphisme et virtualité

Principe

En C++ est apparue la notion de typage dynamique également appelée "polymorphisme". À l’origine, l’idée est d’obtenir d’un pointeur qu’il puisse reconnaître dynamiquement et sans cast le type de l’objet sur lequel il pointe afin de pouvoir accéder à ses membres. Cette possibilité est introduite en C++ mais elle se limite, à partir d’un pointeur sur une classe de base, à autoriser l’accès à certaines des fonctions redéfinies dans ces classes dérivées.

Le polymorphisme obtenu est donc partiel. Néanmoins, il permet d’introduire des listes d’objets de types différents dans les programmes. Typiquement, c’est un tableau de pointeurs dans lequel chacun pointe non seulement sur un objet différent, mais surtout peut, sans cast, accéder à certaines fonctions de cet objet. Ce changement important obéit à des règles strictes que nous allons explorer et vérifier.

Accès pointeurs limité par son type

Un pointeur ne peut accéder qu’au bloc mémoire défini par son type. Soit une classe Sport et une classe Judo dérivée de Sport :

class Sport { . . . }  
class Judo : public Sport { . . . } 

Par défaut, un pointeur de type Sport* auquel est affectée une adresse de type Judo* n’accède qu’aux données et fonctions membres de Sport :

Judo judo;  
Sport *ptr = &judo;    // ptr reste de type Sport* 

Le programme suivant permet de le constater :

#include <iostream> 
 
class Sport 
{ 
public: 
    std::string lieu; 
    Sport(std::string lieu) :lieu(lieu) {} 
    void Pratique() { std::cout << "Pratique un sport\n"; } 
}; 
class Judo : public Sport 
{ 
public:  
    bool kimono; 
    Judo(std::string lieu, bool kimono) 
        :Sport(lieu), kimono(kimono) {} 
    void Pratique() { std::cout << "Pratique le judo\n"; } 
}; 
 
 
int main() 
{ 
    Sport* sport; 
    Judo judo("Paris", true); 
 
    sport = &judo; 
    sport->Pratique();     // Pratique() de la classe Sport,  
                           // et non de la classe Judo  
 
 
    std::cin.get(); 
    return 0; 
}...

Autorisation d’accès pour les fonctions virtuelles

Le pointeur de type Sport* de l’exemple précédent peut accéder à des fonctions de la classe dérivée Judo à deux conditions :

  • Ces fonctions sont des redéfinitions de fonctions de la classe de base.

  • Dans la classe de base, ces fonctions sont déclarées virtuelles avec l’instruction virtual.

Voici le programme précédent modifié : la fonction Pratique de Sport est déclarée virtuelle :

#include <iostream> 
class Sport 
{ 
public: 
    std::string lieu; 
    Sport(std::string lieu) :lieu(lieu) {} 
    virtual void Pratique() { std::cout << "Pratique un sport\n"; } 
}; 
class Judo : public Sport 
{ 
public: 
    bool kimono; 
    Judo(std::string lieu, bool kimono) 
        :Sport(lieu), kimono(kimono) {} 
    void Pratique() { std::cout << "Pratique le judo\n"; } 
}; 
 
int main() 
{ 
    Sport* sport; 
    Judo judo("Paris", true); 
 
    sport = &judo; 
    sport->Pratique();      // Pratique() de la classe Judo,  
    std::cin.get(); 
    return 0; 
} 

Le programme affiche :

Pratique le judo 

Avec une fonction initiale déclarée virtuelle dans une classe, la détermination de la fonction effective, lors d’un appel via un pointeur d’objet, se fait à l’exécution selon...

Destructeur virtuel

Cette notion de virtualité des fonctions peut s’appliquer à un destructeur afin de pouvoir libérer la mémoire de l’objet réellement pointé par un pointeur :

#include <iostream> 
class Sport 
{ 
public: 
    std::string lieu; 
    Sport(std::string lieu) :lieu(lieu) {} 
    virtual ~Sport() { std::cout << "destructeur classe Sport\n"; } 
    virtual void Pratique() { std::cout << "Pratique un sport\n"; } 
}; 
class Judo : public Sport 
{ 
public: 
    bool kimono; 
    Judo(std::string lieu, bool kimono) 
        :Sport(lieu), kimono(kimono) {} 
    ~Judo() { std::cout << "destructeur classe Judo\n"; } 
    void Pratique() { std::cout << "Pratique le judo\n"; } 
}; 
 
 
int main() 
{ 
    Sport* ptr; 
    ptr = new Sport("Paris"); 
    ptr->Pratique();    // Pratique() de Sport  
    delete ptr;         // destructeur de Sport  
 
 
    ptr = new Judo("Paris", true); 
    ptr->Pratique();    // Pratique() de Judo  
    delete ptr;         // destructeur de Judo puis de Sport  
 
    std::cin.get(); 
    return 0; 
} 

Le programme imprime :...

Intérêt des fonctions virtuelles

L’apport important de ce concept est de permettre de constituer des listes d’objets différents. Par exemple :

#include <iostream> 
 
class Sport 
{ 
public: 
    virtual ~Sport() { std::cout << "Destructeur Sport\n"; } 
    virtual void Pratique() { std::cout << "Pratique un sport\n"; } 
}; 
class Judo : public Sport 
{ 
public: 
    ~Judo() { std::cout << "Destructeur Judo\n"; } 
    void Pratique() { std::cout << "Pratique le judo\n"; } 
}; 
class Tennis : public Sport 
{ 
public: 
    ~Tennis() { std::cout << "Destructeur Tennis\n"; } 
    void Pratique() { std::cout << "Pratique le tennis\n"; } 
}; 
class Football : public Sport 
{ 
public: 
    ~Football() { std::cout << "Destructeur Football\n"; } 
    void Pratique() { std::cout << "Pratique le football\n"; } 
}; 
class Natation : public Sport 
{ 
public: 
    ~Natation() { std::cout << "Destructeur Natation\n"; } 
    void Pratique() { std::cout << "Pratique la natation\n"; } 
}; 
class Cyclisme : public Sport 
{ 
public: 
    ~Cyclisme() { std::cout << "Destructeur Cyclisme\n"; } 
    void Pratique() { std::cout << "Pratique le cyclisme\n"; } 
}; 

Dans...