Techniques d’héritage en C++

Salut ce forum apprends les bases du c++

Moderator: Rick

Post Reply
Hydraxx
Site Admin
Posts: 114
Joined: Mon Jan 12, 2026 4:04 pm
Location: France
Contact:

Techniques d’héritage en C++

Post by Hydraxx »

Techniques d’héritage en C++

1. Introduction à l’héritage

L’héritage permet de créer une nouvelle classe à partir d’une classe existante.

La classe existante est appelée classe de base.
La nouvelle classe est appelée classe dérivée.

Exemple :

Code: Select all

#include <iostream>

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

class Derived : public Base
{
public:
    void world()
    {
        std::cout << "Derived::world()\n";
    }
};

int main()
{
    Derived d;

    d.hello(); // hérité de Base
    d.world(); // propre à Derived
}
Ici, Derived contient la partie Base et ajoute son propre comportement.

On parle souvent de relation is-a :

Code: Select all

Derived est un Base
Exemple logique :

Code: Select all

class Animal {};
class Dog : public Animal {};
Un chien est un animal.

À l’inverse, l’héritage n’est pas adapté à une relation de type has-a.

Exemple :

Code: Select all

class Engine {};

class Car
{
    Engine engine;
};
Une voiture possède un moteur, donc la composition est plus adaptée que l’héritage.


2. public, protected et private

Une classe possède trois niveaux d’accès principaux :

Code: Select all

public
protected
private
public
Accessible depuis n’importe quel code possédant un objet ou une référence valide.

protected
Accessible depuis la classe elle-même et ses classes dérivées.

private
Accessible uniquement depuis la classe qui déclare le membre.

Exemple :

Code: Select all

class Base
{
public:
    int publicValue = 10;

protected:
    int protectedValue = 20;

private:
    int privateValue = 30;
};

class Derived : public Base
{
public:
    void test()
    {
        std::cout << publicValue << '\n';     // OK
        std::cout << protectedValue << '\n';  // OK

        // std::cout << privateValue;         // ERREUR
    }
};
Un membre private de Base existe bien dans l’objet Derived, mais le code de Derived n’a pas le droit d’y accéder directement.

La solution habituelle consiste à exposer une fonction dans Base :

Code: Select all

class Base
{
private:
    int value = 42;

public:
    int getValue() const
    {
        return value;
    }
};

3. Effet du type d’héritage

Avec :

Code: Select all

class Derived : public Base
les membres public restent public et les membres protected restent protected.

Avec :

Code: Select all

class Derived : protected Base
les membres public et protected de Base deviennent protected vus depuis Derived.

Avec :

Code: Select all

class Derived : private Base
les membres public et protected de Base deviennent private vus depuis Derived.

Dans la majorité des hiérarchies polymorphiques classiques, on utilise :

Code: Select all

public Base

4. Ajouter du comportement dans une classe dérivée

Une classe dérivée peut conserver les fonctions de Base et ajouter de nouvelles fonctions.

Code: Select all

class Process
{
public:
    void start()
    {
        std::cout << "start\n";
    }
};

class DebugProcess : public Process
{
public:
    void dumpRegisters()
    {
        std::cout << "register dump\n";
    }
};
DebugProcess possède donc :

Code: Select all

start()
dumpRegisters()

5. Masquage d’une fonction de la classe de base

Une fonction de même nom dans la classe dérivée peut masquer celle de la classe de base.

Code: Select all

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

class Derived : public Base
{
public:
    void print()
    {
        std::cout << "Derived\n";
    }
};

Code: Select all

Derived d;
d.print();
Affiche :

Code: Select all

Derived
Pour appeler explicitement la version de Base :

Code: Select all

d.Base::print();
ou depuis Derived :

Code: Select all

class Derived : public Base
{
public:
    void print()
    {
        Base::print();
        std::cout << "Derived\n";
    }
};

6. Le vrai polymorphisme : virtual

Sans virtual, l’appel est généralement déterminé par le type statique de la variable.

Code: Select all

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

class Derived : public Base
{
public:
    void print()
    {
        std::cout << "Derived\n";
    }
};

Derived d;
Base* p = &d;

p->print();
Sans virtual, l’appel vise Base::print().

Pour obtenir un comportement polymorphique :

Code: Select all

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

class Derived : public Base
{
public:
    void print() override
    {
        std::cout << "Derived\n";
    }
};
Alors :

Code: Select all

Derived d;
Base* p = &d;

p->print();
appelle :

Code: Select all

Derived::print()
Le type de p est Base*, mais l’objet réel pointé est Derived.

On distingue donc :

Code: Select all

Type statique  : connu à la compilation
Type dynamique : type réel de l’objet à l’exécution

7. override

Le mot-clé override indique au compilateur :

Code: Select all

"Cette fonction doit réellement redéfinir une fonction virtuelle de la base."
Exemple :

Code: Select all

class Base
{
public:
    virtual void run(int x);
};

class Derived : public Base
{
public:
    void run(int x) override;
};
Si on écrit par erreur :

Code: Select all

void run(double x) override;
le compilateur signale une erreur.

Sans override, on risque de créer involontairement une nouvelle fonction au lieu de redéfinir celle de Base.

En pratique moderne :

Code: Select all

virtual dans la classe de base
override dans les classes dérivées

8. final

final peut empêcher l’héritage :

Code: Select all

class Base final
{
};
Impossible ensuite de faire :

Code: Select all

class Derived : public Base {};
final peut aussi empêcher une nouvelle redéfinition :

Code: Select all

class Base
{
public:
    virtual void run();
};

class Derived : public Base
{
public:
    void run() override final;
};
Une classe dérivée de Derived ne pourra plus redéfinir run().


9. Fonctionnement interne : vptr et vtable

Le standard C++ n’impose pas une implémentation précise des fonctions virtuelles.

Cependant, les compilateurs modernes utilisent généralement une structure conceptuellement proche de :

Code: Select all

Objet
|
+-- vptr ---------> vtable
|                  +-- adresse fonction virtuelle 1
|                  +-- adresse fonction virtuelle 2
|                  +-- adresse destructeur virtuel
|
+-- données
Le vptr est un pointeur stocké dans l’objet polymorphique.

La vtable est une table contenant les adresses des fonctions virtuelles correspondant au type réel de l’objet.

Exemple conceptuel :

Code: Select all

class Base
{
public:
    virtual void f();
    virtual void g();
};

class Derived : public Base
{
public:
    void f() override;
};
La table de Derived peut conceptuellement ressembler à :

Code: Select all

Derived vtable
+----------------------+
| &Derived::f          |
| &Base::g             |
+----------------------+
Derived remplace f mais hérite encore de g.


10. Coût du polymorphisme dynamique

Une classe polymorphique possède généralement au moins un pointeur supplémentaire par sous-objet polymorphique.

Sur x64, cela représente généralement 8 octets pour un vptr.

Un appel virtuel implique généralement un appel indirect.


Pour du code système, il est utile de savoir que virtual n’est pas "gratuit", mais son coût reste souvent faible comparé au coût d’une abstraction mal conçue.


11. Destructeurs virtuels

Si une classe est destinée à être détruite via un pointeur vers la base, son destructeur doit généralement être virtuel.

Mauvais exemple :

Code: Select all

class Base
{
public:
    ~Base() {}
};

class Derived : public Base
{
public:
    ~Derived()
    {
        std::cout << "cleanup Derived\n";
    }
};

Base* p = new Derived;
delete p;
Le comportement est incorrect : le destructeur de Base n’est pas virtuel.

Version correcte :

Code: Select all

class Base
{
public:
    virtual ~Base() = default;
};
Alors :

Code: Select all

Base* p = new Derived;
delete p;
déclenche correctement :

Code: Select all

Derived::~Derived()
Base::~Base()

12. Ordre de construction

Quand on crée un objet dérivé :

Code: Select all

Derived d;
l’ordre général est :
  • Base
  • membres de Derived, dans leur ordre de déclaration
  • corps du constructeur de Derived
Exemple :

Code: Select all

class Base
{
public:
    Base()
    {
        std::cout << "Base\n";
    }
};

class Derived : public Base
{
public:
    Derived()
    {
        std::cout << "Derived\n";
    }
};
Résultat :

Code: Select all

Base
Derived

13. Ordre de destruction

La destruction se fait en sens inverse :
  • corps du destructeur Derived
  • membres Derived en ordre inverse
  • destructeur Base
C’est logique : on détruit d’abord la partie la plus spécialisée, puis la partie fondamentale.


14. Appeler un constructeur parent

On utilise la liste d’initialisation :

Code: Select all

class Base
{
public:
    explicit Base(int value)
    {
        std::cout << value << '\n';
    }
};

class Derived : public Base
{
public:
    Derived(int value)
        : Base(value)
    {
    }
};
Le constructeur Base(value) est exécuté avant le corps de Derived.


15. Ordre réel d’initialisation des membres

L’ordre n’est pas celui écrit dans la liste d’initialisation.

Il dépend de l’ordre de déclaration des membres.

Code: Select all

class Test
{
    int a;
    int b;

public:
    Test()
        : b(20), a(10)
    {
    }
};
Malgré l’écriture :

Code: Select all

b puis a
l’ordre réel est :

Code: Select all

a puis b
car a est déclaré avant b.


16. Constructeurs hérités

On peut importer les constructeurs d’une classe de base :

Code: Select all

class Base
{
public:
    Base(int x) {}
    Base(double d) {}
};

class Derived : public Base
{
public:
    using Base::Base;
};
Alors :

Code: Select all

Derived a(10);
Derived b(3.14);
peut réutiliser directement les constructeurs de Base.

Derived peut également définir ses propres constructeurs.


17. Réutilisation d’une implémentation de Base

Une classe dérivée peut redéfinir une fonction tout en réutilisant la logique de la base :

Code: Select all

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

class Derived : public Base
{
public:
    void initialize() override
    {
        Base::initialize();

        std::cout << "Derived init\n";
    }
};
Ici, Derived étend le comportement au lieu de le remplacer entièrement.


18. Polymorphisme avec références

Le polymorphisme ne nécessite pas forcément des pointeurs.

Code: Select all

void execute(Base& object)
{
    object.run();
}
Si run() est virtuelle :

Code: Select all

Derived d;
execute(d);
appelle Derived::run().


19. Polymorphisme et collections

On peut stocker des objets différents derrière une interface commune.

Exemple moderne :

Code: Select all

#include <memory>
#include <vector>

std::vector<std::unique_ptr<Base>> objects;

objects.push_back(std::make_unique<DerivedA>());
objects.push_back(std::make_unique<DerivedB>());

for (auto& object : objects)
{
    object->run();
}
Chaque objet exécute sa propre version de run().


20. Object slicing

Un piège important :

Code: Select all

Derived d;
Base b = d;
Ici, b est un nouvel objet Base.

La partie spécifique de Derived est perdue.

C’est ce qu’on appelle :

Code: Select all

object slicing
Pour préserver le polymorphisme, on manipule plutôt :

Code: Select all

Base*
Base&
std::unique_ptr<Base>
std::shared_ptr<Base>

21. Classes abstraites

Une fonction virtuelle pure s’écrit :

Code: Select all

virtual void run() = 0;
Exemple :

Code: Select all

class Device
{
public:
    virtual void start() = 0;
    virtual ~Device() = default;
};
Device est alors une classe abstraite.

On ne peut pas écrire :

Code: Select all

Device d;
Une classe dérivée doit implémenter start() pour devenir concrète :

Code: Select all

class Disk : public Device
{
public:
    void start() override
    {
        std::cout << "Disk start\n";
    }
};

22. Une fonction virtuelle pure peut avoir une définition

Il est possible de déclarer :

Code: Select all

class Base
{
public:
    virtual void f() = 0;
};
puis de fournir quand même une définition hors de la classe :

Code: Select all

void Base::f()
{
    std::cout << "Base implementation\n";
}
La classe reste abstraite.

Une classe dérivée peut appeler explicitement :

Code: Select all

Base::f();
C’est rare mais valide.


23. Upcasting

L’upcast consiste à convertir un objet dérivé vers sa base.

Code: Select all

Derived d;
Base* p = &d;
C’est généralement implicite et sûr dans un héritage public non ambigu.

On peut aussi écrire :

Code: Select all

Base& ref = d;
L’objet réel reste Derived.


24. Downcasting

Le downcast fait l’inverse :

Code: Select all

Base* -> Derived*
Il est plus dangereux car rien ne garantit que l’objet réel soit Derived.

Exemple :

Code: Select all

Base* base = ...;
Derived* derived = static_cast<Derived*>(base);
static_cast ne vérifie pas le type dynamique.

Si base ne pointe pas réellement vers un Derived compatible, utiliser le résultat peut provoquer un comportement indéfini.


25. dynamic_cast

dynamic_cast effectue une vérification à l’exécution.

Code: Select all

Base* base = ...;

Derived* derived = dynamic_cast<Derived*>(base);

if (derived)
{
    derived->specialFunction();
}
Si le cast échoue :

Code: Select all

derived == nullptr
Pour une référence :

Code: Select all

Derived& d = dynamic_cast<Derived&>(baseRef);
si le cast échoue, C++ lance :

Code: Select all

std::bad_cast
Pour un dynamic_cast descendant, la classe de base doit être polymorphique, typiquement avec au moins une fonction virtuelle.


26. RTTI

RTTI signifie :

Code: Select all

Run-Time Type Information
Deux mécanismes importants :

Code: Select all

dynamic_cast
typeid
Exemple :

Code: Select all

#include <typeinfo>

Base& ref = derived;

std::cout << typeid(ref).name();
Si Base est polymorphique, typeid(ref) peut refléter le type dynamique réel de l’objet.

Le texte produit par name() dépend du compilateur.


27. Éviter d’abuser de typeid

Un code du genre :

Code: Select all

if (typeid(x) == typeid(Dog))
{
    ...
}
else if (typeid(x) == typeid(Cat))
{
    ...
}
peut indiquer que le polymorphisme devrait être utilisé directement.

On préfère souvent :

Code: Select all

virtual void speak() = 0;
puis :

Code: Select all

animal.speak();

28. Héritage multiple

C++ permet plusieurs classes de base :

Code: Select all

class Reader
{
public:
    void read() {}
};

class Writer
{
public:
    void write() {}
};

class Device : public Reader, public Writer
{
};
Device contient alors les sous-objets Reader et Writer.


29. Ambiguïtés de noms

Si deux classes de base possèdent une fonction de même nom :

Code: Select all

class A
{
public:
    void run() {}
};

class B
{
public:
    void run() {}
};

class C : public A, public B
{
};
Alors :

Code: Select all

C c;
// c.run(); // ambigu
Il faut préciser :

Code: Select all

c.A::run();
c.B::run();

30. Le problème du diamant

Considérons :

Code: Select all

        Animal
       /      \
     Dog      Bird
       \      /
        DogBird
Sans héritage virtuel :

Code: Select all

class Dog : public Animal {};
class Bird : public Animal {};
class DogBird : public Dog, public Bird {};
DogBird contient alors potentiellement deux sous-objets Animal :

Code: Select all

DogBird
+-- Dog
|   +-- Animal
|
+-- Bird
    +-- Animal
Cela provoque des ambiguïtés.


31. Héritage virtuel

On peut partager une seule base commune :

Code: Select all

class Dog : public virtual Animal {};
class Bird : public virtual Animal {};
class DogBird : public Dog, public Bird {};
Conceptuellement :

Code: Select all

        Animal
       /      \
     Dog      Bird
       \      /
        DogBird
mais DogBird ne possède qu’un seul sous-objet Animal partagé.

L’implémentation mémoire devient cependant plus complexe.


32. Construction d’une base virtuelle

Avec une base virtuelle, c’est la classe la plus dérivée qui est responsable de la construction de la base virtuelle.

Exemple :

Code: Select all

class Animal
{
public:
    Animal(int weight) {}
};

class Dog : public virtual Animal
{
public:
    Dog(int weight)
        : Animal(weight)
    {
    }
};

class Bird : public virtual Animal
{
public:
    Bird(int weight)
        : Animal(weight)
    {
    }
};

class DogBird : public Dog, public Bird
{
public:
    DogBird(int weight)
        : Animal(weight),
          Dog(weight),
          Bird(weight)
    {
    }
};
Dans DogBird, c’est DogBird qui construit réellement la base virtuelle Animal.


33. Types de retour covariants

Une fonction virtuelle peut parfois avoir un type de retour plus spécifique dans la dérivée.

Exemple :

Code: Select all

class Base
{
public:
    virtual Base* clone();
};

class Derived : public Base
{
public:
    Derived* clone() override;
};
Derived* est plus spécifique que Base*.

Cela fonctionne notamment avec des pointeurs et références de classes liées par héritage.


34. Masquage des overloads

Supposons :

Code: Select all

class Base
{
public:
    void f(int);
    void f(double);
};

class Derived : public Base
{
public:
    void f(const char*);
};
Le simple fait de déclarer f dans Derived peut masquer les f de Base lors de la recherche de nom.

Pour les réintroduire :

Code: Select all

class Derived : public Base
{
public:
    using Base::f;

    void f(const char*);
};

35. Ajouter une surcharge à une fonction virtuelle

On peut combiner :

Code: Select all

using Base::f;
avec de nouvelles surcharges.

Exemple :

Code: Select all

class Base
{
public:
    virtual void f(int);
};

class Derived : public Base
{
public:
    using Base::f;

    void f(int) override;
    void f(double);
};
Cela évite de masquer involontairement les versions de Base.


36. Fonctions static et héritage

Une fonction membre static ne possède pas de pointeur this.

Elle n’est pas virtuelle.

Exemple :

Code: Select all

class Base
{
public:
    static void info()
    {
        std::cout << "Base\n";
    }
};

class Derived : public Base
{
public:
    static void info()
    {
        std::cout << "Derived\n";
    }
};
Il s’agit d’un masquage, pas de polymorphisme dynamique.


37. Une fonction virtuelle private peut être override

C’est un point subtil.

Code: Select all

class Base
{
private:
    virtual void run()
    {
        std::cout << "Base\n";
    }

public:
    void execute()
    {
        run();
    }
};

class Derived : public Base
{
private:
    void run() override
    {
        std::cout << "Derived\n";
    }
};
Puis :

Code: Select all

Derived d;
d.execute();
Base::execute() appelle virtuellement run(), donc Derived::run() est sélectionnée.

Le fait que Base::run soit private empêche son appel direct depuis Derived, mais n’empêche pas la redéfinition virtuelle.


38. Arguments par défaut et virtual

Voici un piège classique.

Code: Select all

class Base
{
public:
    virtual void f(int x = 10)
    {
        std::cout << "Base " << x << '\n';
    }
};

class Derived : public Base
{
public:
    void f(int x = 20) override
    {
        std::cout << "Derived " << x << '\n';
    }
};
Puis :

Code: Select all

Derived d;
Base* p = &d;

p->f();
La fonction choisie dynamiquement est :

Code: Select all

Derived::f
mais l’argument par défaut est généralement choisi selon le type statique :

Code: Select all

Base*
Donc la valeur utilisée est 10.

On obtient conceptuellement :

Code: Select all

Derived 10
C’est pour cela qu’il vaut mieux éviter de modifier les arguments par défaut dans les overrides.


39. Changer l’accessibilité d’un membre hérité

using peut changer l’exposition d’un membre hérité dans certains cas.

Exemple :

Code: Select all

class Base
{
protected:
    void helper() {}
};

class Derived : public Base
{
public:
    using Base::helper;
};
helper devient publiquement accessible via Derived.

Cela ne change pas la déclaration d’origine dans Base.


40. Constructeur de copie et héritage

Lorsqu’une classe dérivée possède un constructeur de copie personnalisé, il faut aussi copier la partie Base.

Exemple :

Code: Select all

class Base
{
public:
    Base(const Base&) = default;
};

class Derived : public Base
{
    int value;

public:
    Derived(const Derived& other)
        : Base(other),
          value(other.value)
    {
    }
};
Si on oublie Base(other), la partie base peut être initialisée autrement que prévu selon les constructeurs disponibles.


41. Opérateur d’affectation et héritage

Même principe :

Code: Select all

Derived& operator=(const Derived& other)
{
    if (this != &other)
    {
        Base::operator=(other);
        value = other.value;
    }

    return *this;
}
La partie Base doit être correctement affectée elle aussi.


42. Héritage non public

L’héritage private peut exprimer :

Code: Select all

"Derived est implémenté en utilisant Base"
mais pas forcément :

Code: Select all

"Derived est un Base"
Exemple :

Code: Select all

class Derived : private Base
{
};
Un Derived ne se convertit pas publiquement en Base depuis du code client.

Pour une relation "has-a", la composition reste souvent plus claire.


43. static_cast

static_cast sert à de nombreuses conversions vérifiées statiquement.

Exemples :

Code: Select all

double d = 3.14;
int i = static_cast<int>(d);
ou :

Code: Select all

Derived d;
Base* b = static_cast<Base*>(&d);
Pour un downcast :

Code: Select all

Derived* d = static_cast<Derived*>(b);
il n’y a pas de vérification dynamique du type réel.


44. dynamic_cast résumé

Utilisation idéale :

Code: Select all

Base* -> Derived*
lorsqu’on veut vérifier le type réel.

Code: Select all

if (Derived* d = dynamic_cast<Derived*>(base))
{
    d->special();
}
Avantage :

Code: Select all

vérification à l’exécution
Inconvénient :

Code: Select all

coût RTTI + hiérarchie polymorphique requise

45. reinterpret_cast

reinterpret_cast sert à des conversions très bas niveau.

Exemple :

Code: Select all

std::uintptr_t address =
    reinterpret_cast<std::uintptr_t>(ptr);
Puis éventuellement :

Code: Select all

void* ptr2 =
    reinterpret_cast<void*>(address);
Autre exemple :

Code: Select all

char* bytes =
    reinterpret_cast<char*>(objectPtr);
reinterpret_cast ne transforme pas magiquement la représentation de l’objet.

Il demande essentiellement au compilateur de considérer une valeur sous une autre forme de pointeur ou d’entier autorisée par les règles du langage.

Une mauvaise utilisation peut mener à :

Code: Select all

comportement indéfini
problèmes d’alignement
violation des règles d’aliasing
lecture invalide
crash

46. const_cast

const_cast permet d’ajouter ou retirer const/volatile.

Exemple :

Code: Select all

const int* p = ...;

int* q = const_cast<int*>(p);
Attention :

Si l’objet original est réellement const, le modifier via q produit un comportement indéfini.

Exemple dangereux :

Code: Select all

const int x = 10;

int* p = const_cast<int*>(&x);
*p = 20;

47. std::bit_cast

std::bit_cast copie la représentation binaire d’un type vers un autre type compatible en taille.

Exemple :

Code: Select all

#include <bit>
#include <cstdint>

float f = 1.0f;

std::uint32_t bits =
    std::bit_cast<std::uint32_t>(f);
On récupère les bits IEEE-754 du float sans violer les règles d’aliasing.

Les tailles doivent correspondre :

Code: Select all

sizeof(To) == sizeof(From)
C’est particulièrement utile pour :

Code: Select all

formats binaires
sérialisation contrôlée
analyse de représentation
code bas niveau

48. Comparatif rapide des casts

Code: Select all

static_cast
    conversion connue à la compilation
    conversions numériques
    conversions dans une hiérarchie
    pas de contrôle dynamique du downcast

dynamic_cast
    vérification runtime
    hiérarchie polymorphique
    nullptr ou bad_cast en cas d’échec

reinterpret_cast
    réinterprétation bas niveau
    pointeurs / entiers
    très peu de sécurité

const_cast
    ajoute ou retire const/volatile

std::bit_cast
    copie la représentation binaire
    types de même taille

49. Exemple système : interface polymorphique

Même si on préfères généralement une approche procédurale, il est utile de comprendre ce modèle car énormément de code Windows et C++ l’utilise conceptuellement.

Code: Select all

class HandleObject
{
public:
    virtual void close() = 0;
    virtual ~HandleObject() = default;
};

class FileHandle : public HandleObject
{
public:
    void close() override
    {
        // CloseHandle(...)
    }
};

class SocketHandle : public HandleObject
{
public:
    void close() override
    {
        // closesocket(...)
    }
};
Un code commun peut alors faire :

Code: Select all

void cleanup(HandleObject& object)
{
    object.close();
}

50. virtual dans constructeurs et destructeurs

Il faut être prudent avec les fonctions virtuelles appelées pendant la construction ou la destruction.

Pendant le constructeur de Base, la partie Derived n’est pas encore complètement construite.

Le dispatch virtuel ne doit donc pas être imaginé comme si l’objet final Derived était déjà entièrement disponible.

Exemple :

Code: Select all

class Base
{
public:
    Base()
    {
        run();
    }

    virtual void run()
    {
        std::cout << "Base\n";
    }
};
Éviter de dépendre d’un comportement polymorphique complet depuis les constructeurs et destructeurs.


51. Héritage ou composition ?

Utiliser l’héritage lorsque la relation est réellement :

Code: Select all

Derived est un Base
Utiliser la composition lorsque :

Code: Select all

Object possède un Component
Exemple :

Code: Select all

class Scheduler
{
};

class ProcessManager
{
    Scheduler scheduler;
};
ProcessManager n’est pas un Scheduler.

Il en utilise un.


52. Bonnes pratiques modernes
  • utiliser override pour chaque override
  • destructeur virtuel si destruction polymorphique
  • éviter les downcasts quand le design peut être polymorphique
  • préférer unique_ptr pour la propriété unique
  • éviter les hiérarchies profondes inutilement
  • utiliser final lorsque la dérivation n’a pas de sens
  • éviter les arguments par défaut différents dans les overrides
  • faire attention au slicing
  • utiliser dynamic_cast uniquement lorsque réellement nécessaire
  • préférer composition à héritage quand la relation n’est pas "is-a"

Return to “Bases du C++”

Who is online

Users browsing this forum: No registered users and 0 guests