Derived classes
Tout type de classe (qu'il soit déclaré avec class-key class ou struct ) peut être déclaré comme dérivé d'une ou plusieurs classes de base qui, à leur tour, peuvent être dérivées de leurs propres classes de base, formant une hiérarchie d'héritage.
Table des matières |
Syntaxe
La liste des classes de base est fournie dans la
base-clause
de la
syntaxe de déclaration de classe
. La
base-clause
consiste en le caractère
:
suivi d'une liste séparée par des virgules d'un ou plusieurs
base-specifier
s.
| attr (facultatif) class-or-computed | (1) | ||||||||
attr
(facultatif)
virtual
class-or-computed
|
(2) | ||||||||
| attr (facultatif) access-specifier class-or-computed | (3) | ||||||||
attr
(facultatif)
virtual
access-specifier
class-or-computed
|
(4) | ||||||||
attr
(facultatif)
access-specifier
virtual
class-or-computed
|
(5) | ||||||||
virtual
et
access-specifier
peuvent apparaître dans n'importe quel ordre.
| attr | - | (depuis C++11) séquence d'un nombre quelconque d' attributs | ||||
| access-specifier | - | l'un des private , public , ou protected | ||||
| class-or-computed | - |
l'un des
|
Un spécificateur de type élaboré ne peut pas apparaître directement en tant que class-or-computed en raison de limitations syntaxiques.
|
base-specifier s dans une base-clause peuvent être des pack expansions .
Une classe ou struct déclarée
|
(depuis C++11) |
Si access-specifier est omis, il prend par défaut la valeur public pour les classes dérivées déclarées avec class-key struct et la valeur private pour les classes dérivées déclarées avec class-key class .
struct Base { int a, b, c; }; // chaque objet de type Derived inclut Base comme sous-objet struct Derived : Base { int b; }; // chaque objet de type Derived2 inclut Derived et Base comme sous-objets struct Derived2 : Derived { int c; };
Les classes désignées par class-or-computed listées dans la base-clause sont des classes de base directes. Leurs bases sont des classes de base indirectes. La même classe ne peut pas être spécifiée comme classe de base directe plus d'une fois, mais la même classe peut être à la fois classe de base directe et indirecte.
Chaque classe de base directe et indirecte est présente, sous forme de sous-objet de classe de base , dans la représentation objet de la classe dérivée à un décalage dépendant de l'ABI. Les classes de base vides n'augmentent généralement pas la taille de l'objet dérivé grâce à l' optimisation des classes de base vides . Les constructeurs des sous-objets de classe de base sont appelés par le constructeur de la classe dérivée : des arguments peuvent être fournis à ces constructeurs dans la liste d'initialisation des membres .
Classes de base virtuelles
Pour chaque classe de base distincte spécifiée virtual , l' objet le plus dérivé contient un seul sous-objet de classe de base de ce type, même si la classe apparaît plusieurs fois dans la hiérarchie d'héritage (à condition qu'elle soit héritée virtual à chaque fois).
struct B { int n; }; class X : public virtual B {}; class Y : virtual public B {}; class Z : public B {}; // chaque objet de type AA possède un X, un Y, un Z et deux B : // un qui est la base de Z et un qui est partagé par X et Y struct AA : X, Y, Z { AA() { X::n = 1; // modifie le membre du sous-objet virtuel B Y::n = 2; // modifie le même membre du sous-objet virtuel B Z::n = 3; // modifie le membre du sous-objet non virtuel B std::cout << X::n << Y::n << Z::n << '\n'; // affiche 223 } };
Un exemple de hiérarchie d'héritage avec des classes de base virtuelles est la hiérarchie des iostreams de la bibliothèque standard : std::istream et std::ostream sont dérivés de std::ios en utilisant l'héritage virtuel. std::iostream est dérivé à la fois de std::istream et de std::ostream , donc chaque instance de std::iostream contient un sous-objet std::ostream , un sous-objet std::istream , et un seul sous-objet std::ios (et, par conséquent, un seul std::ios_base ).
Tous les sous-objets de base virtuels sont initialisés avant tout sous-objet de base non virtuel, donc seule la classe la plus dérivée appelle les constructeurs des bases virtuelles dans sa liste d'initialisation des membres :
struct B { int n; B(int x) : n(x) {} }; struct X : virtual B { X() : B(1) {} }; struct Y : virtual B { Y() : B(2) {} }; struct AA : X, Y { AA() : B(3), X(), Y() {} }; // le constructeur par défaut de AA appelle les constructeurs par défaut de X et Y // mais ces constructeurs n'appellent pas le constructeur de B car B est une base virtuelle AA a; // a.n == 3 // le constructeur par défaut de X appelle le constructeur de B X x; // x.n == 1
Il existe des règles spéciales pour la recherche de nom non qualifié pour les membres de classe lorsque l'héritage virtuel est impliqué (parfois appelées règles de dominance).
Héritage public
Lorsqu'une classe utilise le public spécificateur d'accès membre pour dériver d'une base, tous les membres publics de la classe de base sont accessibles en tant que membres publics de la classe dérivée et tous les membres protégés de la classe de base sont accessibles en tant que membres protégés de la classe dérivée (les membres privés de la base ne sont jamais accessibles sauf en cas de friend).
L'héritage public modélise la relation de sous-typage de la programmation orientée objet : l'objet de la classe dérivée EST-UN objet de la classe de base. Les références et pointeurs vers un objet dérivé doivent pouvoir être utilisés par tout code qui attend des références ou pointeurs vers l'une de ses bases publiques (voir LSP ) ou, en termes de DbC , une classe dérivée doit maintenir les invariants de classe de ses bases publiques, ne doit pas renforcer de précondition ni affaiblir de postcondition d'une fonction membre qu'elle override .
#include <iostream> #include <string> #include <vector> struct MenuOption { std::string title; }; // Menu est un vecteur de MenuOption : les options peuvent être insérées, supprimées, réorganisées... // et possède un titre. class Menu : public std::vector<MenuOption> { public: std::string title; void print() const { std::cout << title << ":\n"; for (std::size_t i = 0, s = size(); i < s; ++i) std::cout << " " << (i + 1) << ". " << at(i).title << '\n'; } }; // Note : Menu::title n'est pas problématique car son rôle est indépendant de la classe de base. enum class Color { WHITE, RED, BLUE, GREEN }; void apply_terminal_color(Color) { /* OS-specific */ } // CECI EST MAUVAIS ! // ColorMenu est un Menu où chaque option a une couleur personnalisée. class ColorMenu : public Menu { public: std::vector<Color> colors; void print() const { std::cout << title << ":\n"; for (std::size_t i = 0, s = size(); i < s; ++i) { std::cout << " " << (i + 1) << ". "; apply_terminal_color(colors[i]); std::cout << at(i).title << '\n'; apply_terminal_color(Color::WHITE); } } }; // ColorMenu nécessite les invariants suivants qui ne peuvent pas être satisfaits // en héritant publiquement de Menu, par exemple : // - ColorMenu::colors et Menu doivent avoir le même nombre d'éléments // - Pour avoir du sens, l'appel à erase() devrait aussi supprimer les éléments de colors, // pour permettre aux options de conserver leurs couleurs // Fondamentalement, chaque appel non-constant à une méthode std::vector brisera l'invariant // de ColorMenu et nécessitera une correction de l'utilisateur en gérant correctement les couleurs. int main() { ColorMenu color_menu; // Le gros problème de cette classe est que nous devons maintenir ColorMenu::Color // synchronisé avec Menu. color_menu.push_back(MenuOption{"Some choice"}); // color_menu.print(); // ERREUR ! colors[i] dans print() est hors limites color_menu.colors.push_back(Color::RED); color_menu.print(); // OK : colors et Menu ont le même nombre d'éléments }
Héritage protégé
Lorsqu'une classe utilise protected member access specifier pour dériver d'une base, tous les membres publics et protégés de la classe de base sont accessibles en tant que membres protégés de la classe dérivée (les membres privés de la base ne sont jamais accessibles sauf en cas de relation d'amitié).
L'héritage protégé peut être utilisé pour le « polymorphisme contrôlé » : au sein des membres de Derived, ainsi que dans les membres de toutes les classes ultérieurement dérivées, la classe dérivée EST-UNE base : les références et pointeurs vers Derived peuvent être utilisés là où des références et pointeurs vers Base sont attendus.
Héritage privé
Lorsqu'une classe utilise private member access specifier pour dériver d'une classe de base, tous les membres publics et protégés de la classe de base sont accessibles en tant que membres privés de la classe dérivée (les membres privés de la base ne sont jamais accessibles sauf en cas de relation d'amitié).
L'héritage privé est couramment utilisé dans la conception par politiques, puisque les politiques sont généralement des classes vides, et leur utilisation comme bases permet à la fois le polymorphisme statique et tire parti de l' optimisation de base vide .
L'héritage privé peut également être utilisé pour implémenter la relation de composition (le sous-objet de la classe de base est un détail d'implémentation de l'objet de la classe dérivée). L'utilisation d'un membre offre une meilleure encapsulation et est généralement préférée, sauf si la classe dérivée nécessite un accès aux membres protégés (y compris les constructeurs) de la base, a besoin de redéfinir un membre virtuel de la base, nécessite que la base soit construite avant et détruite après un autre sous-objet de base, a besoin de partager une base virtuelle ou doit contrôler la construction d'une base virtuelle. L'utilisation de membres pour implémenter la composition n'est pas non plus applicable dans le cas d'un héritage multiple à partir d'un parameter pack ou lorsque les identités des classes de base sont déterminées à la compilation via la métaprogrammation de templates.
Similaire à l'héritage protégé, l'héritage privé peut également être utilisé pour le polymorphisme contrôlé : au sein des membres de la classe dérivée (mais pas dans les classes ultérieurement dérivées), la classe dérivée EST-UNE classe de base.
template<typename Transport> class service : private Transport // héritage privé de la politique de transport { public: void transmit() { this->send(...); // envoi en utilisant le transport fourni } }; // Politique de transport TCP class tcp { public: void send(...); }; // Politique de transport UDP class udp { public: void send(...); }; service<tcp> service(host, port); service.transmit(...); // envoi via TCP
Recherche de nom de membre
Les règles de recherche de noms non qualifiés et qualifiés pour les membres de classe sont détaillées dans name lookup .
Mots-clés
Rapports de défauts
Les rapports de défauts modifiant le comportement suivants ont été appliqués rétroactivement aux normes C++ précédemment publiées.
| DR | Applicable à | Comportement publié | Comportement corrigé |
|---|---|---|---|
| CWG 1710 | C++98 |
la syntaxe de
class-or-decltype
rendait impossible de dériver d'une
classe dépendante où le désambiguïsateur template est requis |
autorisé template |