Menu

Héritage en C++ : classes de base et dérivées expliquées

Découvrez comment l'héritage en C++ permet à une classe dérivée de réutiliser et d'étendre une classe de base : la syntaxe, l'héritage public et privé, l'ordre des constructeurs et destructeurs, et les pièges comme le slicing d'objets.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

Réutiliser une classe en l'étendant

Vous avez construit des classes avec des constructeurs et les avez nettoyées avec des destructeurs. L'héritage est l'étape suivante : au lieu de copier les membres d'une classe dans une autre, vous déclarez qu'une nouvelle classe est une version spécialisée d'une classe existante et vous la laissez tout hériter automatiquement.

La classe existante est la classe de base (ou parent) ; la nouvelle est la classe dérivée (ou enfant). Une classe dérivée commence avec l'ensemble des données et du comportement de la base, puis ajoute ou modifie ce qui la rend différente. C'est l'outil principal du C++ pour la relation « est-un » : un Dog est un Animal, un SavingsAccount est un BankAccount.

Syntaxe de base : class Derived : public Base

Vous héritez en faisant suivre le nom de la classe dérivée par un deux-points, un spécificateur d'accès et le nom de la classe de base. La forme la plus courante est l'héritage public.

Dog ne déclare jamais name ni eat(), et pourtant tous deux fonctionnent sur un objet Dog parce qu'ils ont été hérités de Animal. La classe dérivée est libre d'ajouter des membres comme bark() dont la base ignore tout.

protected : des membres réservés aux enfants

Un membre private de la base n'est pas accessible à l'intérieur de la classe dérivée - l'héritage ne casse pas l'encapsulation. Lorsque vous voulez que les rouages internes de la base soient cachés du monde extérieur mais disponibles pour les sous-classes, utilisez le spécificateur d'accès protected.

Pensez aux trois niveaux comme à des anneaux concentriques : private signifie « cette classe seulement », protected signifie « cette classe et ses descendants » et public signifie « tout le monde ». Consultez les spécificateurs d'accès pour le tableau complet.

Ordre des constructeurs et destructeurs

Un objet dérivé contient un sous-objet de base, et cette partie de base doit être vivante avant que la partie dérivée ne soit construite. C'est pourquoi la construction commence par la base et la destruction commence par le dérivé (l'ordre exactement inverse). Si la base a besoin d'arguments de constructeur, vous les transmettez via la liste d'initialisation des membres.

La sortie rend l'ordre concret :

Animal ctor: Rex   // la base est construite en premier
Dog ctor           // puis la partie dérivée
Dog dtor           // détruite dans l'ordre inverse...
Animal dtor: Rex   // ...la base en dernier

Si vous oubliez le : Animal(n) et que la base n'a pas de constructeur par défaut, le code ne compilera pas - le C++ n'a aucune idée de la façon de construire la partie de base. Une classe de base dont vous comptez hériter devrait presque toujours déclarer un destructeur (et, comme le montre la page suivante, souvent un destructeur virtuel).

Redéfinition : redéfinir une méthode de base

Une classe dérivée peut remplacer une méthode héritée en en déclarant une avec la même signature. Vous pouvez toujours atteindre l'originale via Base::method().

Il s'agit d'un simple masquage de nom (name hiding), pas de polymorphisme : quelle describe() s'exécute est décidé à la compilation par le type statique de la variable. C'est une limitation cruciale - si vous appelez via un Shape& ou un Shape* qui pointe en réalité vers un Circle, vous obtiendrez quand même Shape::describe(). Corriger cela nécessite virtual, qui est le sujet de la page suivante.

Attention au slicing d'objets

Comme une référence ou un pointeur de base peut désigner un objet dérivé, il est tentant de copier un objet dérivé dans une variable de type base. Ne le faites pas - la partie dérivée se fait découper (sliced).

a est un véritable Animal, pas un Dog portant une étiquette de base, donc breed n'existe tout simplement pas sur lui. Pour travailler avec des objets dérivés de manière polymorphe, vous devez utiliser une référence de base (Animal&) ou un pointeur (Animal*), jamais une valeur de base. Le slicing est silencieux - il compile proprement et se contente de supprimer discrètement des données -, ce qui en fait l'un des bugs d'héritage les plus faciles à expédier en production.

Erreurs courantes à éviter

  • S'attendre à ce que les membres private de la base soient accessibles dans l'enfant. Ils ne le sont pas. Utilisez protected pour les données dont la classe dérivée a légitimement besoin, et gardez l'état réellement interne en private.
  • Oublier de transmettre les arguments du constructeur de base. Si la base n'a pas de constructeur par défaut, vous devez l'appeler explicitement dans la liste d'initialisation du constructeur dérivé (: Base(args)).
  • Slicer un objet dérivé dans une valeur de base. Copier un Dog dans un Animal supprime tout ce qui est spécifique à Dog. Passez et stockez plutôt des références ou des pointeurs de base.
  • Abuser de l'héritage pour la réutilisation de code. L'héritage modélise « est-un ». Si la relation est en réalité « a-un » (une Car a un Engine), préférez la composition - un objet membre - plutôt que l'héritage.

Suite : les fonctions virtuelles

La redéfinition que vous venez de voir était résolue à la compilation, donc appeler via un pointeur de base ignorait la version dérivée. La page suivante, fonctions virtuelles, présente le mot-clé virtual et override - le mécanisme qui fait que le type à l'exécution décide quelle méthode s'exécute, débloquant le vrai polymorphisme et expliquant pourquoi les classes de base ont besoin de destructeurs virtuels.

Questions fréquentes

Qu'est-ce que l'héritage en C++ ?

L'héritage vous permet de définir une nouvelle classe (la classe dérivée) à partir d'une classe existante (la classe de base). La classe dérivée récupère automatiquement les données membres et les fonctions membres de la base, et peut en ajouter de nouvelles ou remplacer un comportement existant. Il modélise une relation « est-un » - un Dog est un Animal - et c'est le principal moyen pour le C++ de réutiliser et d'étendre du code à travers des hiérarchies de classes.

Quelle est la différence entre héritage public et privé en C++ ?

Avec l'héritage public (class Dog : public Animal), l'interface publique de la base reste publique dans la classe dérivée ; ainsi un Dog est-un Animal et peut être utilisé partout où un Animal est attendu. Avec l'héritage private, les membres hérités deviennent privés : la classe dérivée réutilise l'implémentation de la base mais n'est pas substituable à celle-ci. L'héritage public est de loin le cas courant ; ne recourez au privé que pour une réutilisation de type « implémenté-en-termes-de ».

Dans quel ordre s'exécutent les constructeurs et destructeurs avec l'héritage en C++ ?

Les constructeurs s'exécutent en commençant par la base : la classe de base est entièrement construite avant que le corps du constructeur dérivé ne s'exécute. Les destructeurs s'exécutent dans l'ordre exactement inverse - le dérivé d'abord, puis la base. Cela garantit que, pendant qu'un objet dérivé est en cours de construction ou de destruction, chaque partie dont il dépend existe déjà (ou existe encore).

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER