Pourquoi les constructeurs ne sont-ils pas hérités?


33

Je ne comprends pas ce que pourraient être les problèmes si un constructeur était hérité d'une classe de base. Cpp Primer Plus dit:

Les constructeurs diffèrent des autres méthodes de classe en ce qu'ils créent de nouveaux objets, alors que d'autres méthodes sont appelées par des objets existants . C'est une des raisons pour lesquelles les constructeurs ne sont pas hérités . Héritage signifie qu'un objet dérivé peut utiliser une méthode de classe de base, mais que, dans le cas des constructeurs, l'objet n'existe pas avant que le constructeur ait effectué son travail.

Je comprends que le constructeur est appelé avant la construction de l’objet.

Comment cela peut-il engendrer des problèmes si une classe enfant hérite (le terme héritage signifie que la classe enfant peut redéfinir la méthode de la classe parent, etc. )

Je comprends qu’il n’est pas nécessaire d’appeler explicitement un constructeur à partir d’un code [même si je n’en ai pas encore conscience.] Sauf lors de la création d’objets. Même alors, vous pouvez le faire en utilisant un mécanisme pour appeler le constuctor parent [In cpp, en utilisant ::ou en utilisant member initialiser list, En java en utilisant super]. En Java, il existe une application pour l'appeler dans la 1ère ligne. Je comprends que cela consiste à s'assurer que l'objet parent est créé en premier, puis que la construction de l'objet enfant se poursuit.

Cela peut l' emporter . Mais, je ne peux pas arriver à des situations où cela peut poser un problème. Si l'enfant hérite du constructeur parent, qu'est-ce qui peut mal tourner?

Donc, est-ce juste pour ne pas hériter des fonctions inutiles. Ou y a-t-il plus?


Pas vraiment à l'aise avec C ++ 11, mais je pense qu'il contient une forme d'héritage de constructeur.
Yannis

3
Gardez à l'esprit que quoi que vous fassiez dans les constructeurs, vous devez faire attention aux destructeurs.
Manoj R

2
Je pense que vous devez définir ce que l'on entend par "hériter d'un constructeur". Toutes les réponses semblent avoir des variations sur ce que "hériter d'un constructeur" signifie, à leur avis. Je pense qu'aucun d'entre eux ne correspond à mon interprétation initiale. La description "dans la zone grise" à partir du dessus "Héritage signifie qu'un objet dérivé peut utiliser une méthode de classe de base" implique que les constructeurs sont hérités. Un objet dérivé peut très certainement utiliser des méthodes de constructeur de classe de base, ALWAYS.
Dunk

1
Merci pour la clarification de l'héritage d'un constructeur, vous pouvez maintenant obtenir des réponses.
Dunk

Réponses:


41

Il ne peut y avoir d'héritage approprié des constructeurs en C ++, car le constructeur d'une classe dérivée doit effectuer des actions supplémentaires qu'un constructeur de classe de base n'a pas à faire et qu'il ignore. Ces actions supplémentaires sont l’initialisation des données membres de la classe dérivée (et dans une implémentation typique, définir également le vpointer pour faire référence aux classes dérivées vtable).

Quand une classe est construite, il y a toujours un certain nombre de choses qui doivent arriver: les constructeurs de la classe de base (le cas échéant) et les membres directs doivent être appelés et s'il y a des fonctions virtuelles, le vpointer doit être défini correctement . Si vous ne fournissez pas de constructeur pour votre classe, le compilateur en créera un qui effectuera les actions requises et rien d'autre. Si vous faites fournir un constructeur, mais manquer sur certaines des actions requises (par exemple, l'initialisation de certains membres), le compilateur ajoutera automatiquement les actions manquantes à votre constructeur. De cette manière, le compilateur s'assure que chaque classe a au moins un constructeur et que chaque constructeur initialise complètement les objets qu'il crée.

En C ++ 11, une forme d '"héritage de constructeur" a été introduite. Vous pouvez indiquer au compilateur de générer pour vous un ensemble de constructeurs prenant les mêmes arguments que les constructeurs de la classe de base et les transmettant simplement à la classe de base.
Bien qu'il s'appelle officiellement héritage, il ne l'est pas vraiment car il existe encore une fonction spécifique à la classe dérivée. Maintenant, il est simplement généré par le compilateur au lieu d’être écrit explicitement par vous.

Cette fonctionnalité fonctionne comme ceci:

struct Base {
    Base(int a) : i(a) {}
    int i;
};

struct Derived : Base {
    Derived(int a, std::string s) : Base(a), m(s) {}

    using Base::Base; // Inherit Base's constructors.
    // Equivalent to:
    //Derived(int a) : Base(a), m() {}

    std::string m;
};

Deriveda maintenant deux constructeurs (sans compter les constructeurs copier / déplacer). Celui qui prend un int et une chaîne et celui qui prend juste un int.


Donc, cela suppose que la classe enfant n'aura pas son propre constructeur? et le constructeur hérité n'est évidemment pas au courant des enfants membres et des initialisations à effectuer
Suvarna Pattayil

C ++ est défini de telle sorte qu'il est impossible pour une classe de ne pas avoir son propre constructeur. C'est pourquoi je ne considère pas le terme «héritage constructeur» comme un héritage. C'est juste une façon d'éviter d'écrire des phrases compliquées fastidieuses.
Bart van Ingen Schenau

2
Je pense que la confusion provient du fait que même un constructeur vide fait du travail en coulisses. Donc le constructeur a vraiment deux parties: Les travaux internes et la partie que vous écrivez. Comme ils ne sont pas bien séparés, les constructeurs ne sont pas hérités.
Sarien

1
S'il vous plaît envisager d'indiquer d'où m()vient, et comment cela changerait si son type était par exemple int.
Déduplicateur

Est-il possible de faire using Base::Baseimplicitement? Cela aurait de grandes conséquences si j'oubliais cette ligne sur une classe dérivée et que je devais hériter du constructeur de toutes les classes dérivées
Post Self

7

Ce que vous entendez par "hériter du constructeur parent" n'est pas clair. Vous utilisez le mot override , ce qui suggère que vous pensez peut-être aux constructeurs qui se comportent comme des fonctions virtuelles polymorphes . Je n'utilise délibérément pas le terme "constructeurs virtuels" car il s'agit d'un nom commun pour un modèle de code dans lequel vous avez en fait besoin d'une instance déjà existante d'un objet pour en créer un autre.

Les constructeurs polymorphes ne sont guère utiles en dehors du modèle "constructeur virtuel" et il est difficile de trouver un scénario concret dans lequel un constructeur polymorphe réel pourrait être utilisé. Un exemple très artificiel qui n'est en aucun cas même valide à distance en C ++ :

struct Base {
  virtual Base(unsigned p1, unsigned p2) {...}
};

struct Derived: public Base {
  Derived(unsigned p1, unsigned p2) : Base(p1, p2) override {...}
};

int main(void) {
  unsigned p1 = 0;
  unsigned p2 = 42;
  Derived *p_d1 = new Base(p1, p2); // This might call Derived(unsigned, unsigned).
  Derived *p_d2 = nullptr;
  p_d2 = new Base(p1, p2); // This might call Derived(unsigned, unsigned) too.
}

Dans ce cas, le constructeur appelé dépend du type concret de la variable en cours de construction ou d’attribution. Il est complexe à détecter lors de l’analyse / de la génération de code et n’a pas d’utilité: vous connaissez le type de béton que vous construisez et vous avez écrit un constructeur spécifique pour la classe dérivée. Le code C ++ valide suivant fait exactement la même chose, est légèrement plus court et est plus explicite:

struct Base {
  Base(unsigned p1, unsigned p2) {...}
};

struct Derived: public Base {
  Derived(unsigned p1, unsigned p2) : Base(p1, p2) {...}
};

int main(void) {
  unsigned p1 = 0;
  unsigned p2 = 42;
  Derived *p_d1 = new Derived(p1, p2); 
  Derived *p_d2 = nullptr;
  p_d2 = new Derived(p1, p2);
}


Une deuxième interprétation, voire une question supplémentaire, est: que se passe-t-il si les constructeurs de la classe Base sont automatiquement présents dans toutes les classes dérivées, sauf s’ils sont explicitement masqués?

Si l'enfant hérite du constructeur parent, qu'est-ce qui peut mal tourner?

Vous devez écrire du code supplémentaire pour masquer un constructeur parent qu'il est incorrect d'utiliser lors de la construction de la classe dérivée. Cela peut arriver lorsque la classe dérivée spécialise la classe de base de manière à ce que certains paramètres deviennent non pertinents.

L'exemple typique est celui des rectangles et des carrés (notez que les carrés et les rectangles ne sont généralement pas substituables à Liskov, donc ce n'est pas un très bon dessin, mais cela met en évidence le problème).

struct Rectangle {
  Rectangle(unsigned width, unsigned height) {...}
};

struct Square : public Rectangle {
  explicit Square(unsigned side) : Rectangle(side, side) {...}
};

Si Square hérite du constructeur à deux valeurs de Rectangle, vous pouvez construire des carrés de hauteur et de largeur différentes ... C'est logiquement faux, vous voulez donc masquer ce constructeur.


3

Pourquoi les constructeurs ne sont-ils pas hérités: la réponse est étonnamment simple: le constructeur de la classe de base "construit" la classe de base et le constructeur de la classe héritée "construit" la classe héritée. Si la classe héritée hérite du constructeur, le constructeur essaiera de construire un objet de type classe de base et vous ne pourrez pas "construire" un objet de type classe héritée.

Quel genre de défaites le but d'hériter une classe.


3

Le problème le plus évident en permettant à la classe dérivée de remplacer le constructeur de la classe de base est que le développeur de la classe dérivée est maintenant responsable de savoir comment construire sa / ses classe (s) de base. Que se passe-t-il lorsque la classe dérivée ne construit pas correctement la classe de base?

En outre, le principe de substitution de Liskov ne s'appliquerait plus car vous ne pouvez plus compter sur votre collection d'objets de classe de base compatibles, car rien ne garantit que la classe de base a été construite correctement ou de manière compatible avec les autres types dérivés.

Il est encore plus compliqué d'ajouter plus d'un niveau d'héritage. Maintenant, votre classe dérivée doit savoir comment construire toutes les classes de base en amont de la chaîne.

Que se passe-t-il ensuite si vous ajoutez une nouvelle classe de base en haut de la hiérarchie d'héritage? Vous devez mettre à jour tous les constructeurs de classe dérivés.


2

Les constructeurs sont fondamentalement différents des autres méthodes:

  1. Ils sont générés si vous ne les écrivez pas.
  2. Tous les constructeurs de classe de base sont appelés implicitement même si vous ne le faites pas manuellement
  3. Vous ne les appelez pas explicitement mais en créant des objets.

Alors pourquoi ne sont-ils pas hérités? Réponse simple: Puisqu'il y a toujours un remplacement, généré ou écrit manuellement.

Pourquoi chaque classe a-t-elle besoin d'un constructeur? C'est une question compliquée et je pense que la réponse dépend du compilateur. Il existe un constructeur "trivial" pour lequel le compilateur n’exige pas qu’il soit appelé semble-t-il. Je pense que c’est la chose la plus proche de ce que vous entendez par héritage, mais pour les trois raisons énoncées ci-dessus, je pense que comparer les constructeurs aux méthodes normales n’est pas vraiment utile. :)


1

Chaque classe a besoin d'un constructeur, même celui par défaut.
C ++ créera pour vous des constructeurs par défaut sauf si vous créez un constructeur spécialisé.
Si votre classe de base utilise un constructeur spécialisé, vous devez écrire le constructeur spécialisé sur la classe dérivée, même si les deux sont identiques, et les relier.
C ++ 11 vous permet d'éviter la duplication de code sur les constructeurs en utilisant :

Class A : public B {
using B:B;
....
  1. Un ensemble de constructeurs hérités est composé de

    • Tous les constructeurs non modèles de la classe de base (après avoir omis les paramètres de points de suspension, le cas échéant) (depuis C ++ 14)
    • Pour chaque constructeur avec des arguments par défaut ou le paramètre points de suspension, toutes les signatures de constructeur formées en supprimant les points de suspension et en omettant les arguments par défaut à la fin des listes d'arguments, une par une.
    • Tous les modèles de constructeur de la classe de base (après avoir omis les paramètres de points de suspension, le cas échéant) (depuis C ++ 14)
    • Pour chaque modèle de constructeur avec des arguments par défaut ou des points de suspension, toutes les signatures de constructeur formées en supprimant les points de suspension et en omettant les arguments par défaut à la fin des listes d'arguments
  2. Tous les constructeurs hérités qui ne sont pas le constructeur par défaut ou le constructeur copier / déplacer et dont les signatures ne correspondent pas aux constructeurs définis par l'utilisateur dans la classe dérivée, sont implicitement déclarés dans la classe dérivée. Les paramètres par défaut ne sont pas hérités


0

Vous pouvez utiliser:

MyClass() : Base()

Vous demandez pourquoi vous devez faire cela?

La sous-classe peut avoir des propriétés supplémentaires qu'il peut être nécessaire d'initialiser dans le constructeur ou elle peut initialiser les variables de la classe de base d'une manière différente.

Sinon, comment créeriez-vous l'objet de sous-type?


Merci. En fait, j'essaie de comprendre pourquoi un constructeur n'est pas hérité de la classe enfant, contrairement aux autres méthodes de la classe parent.
Suvarna Pattayil
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.