70 votes

Héritage C++ Constructeur/Destructeur

EDIT : Résumé des réponses

Dans ce qui suit, B est une sous-classe de A.

C'est une question de terminologie ; les ctors et les dtors sont pas hérité, dans le sens où le ctor/dtor de B sera pas être emprunté à l'interface de A. Une classe a au moins un constructeur, et a exactement un destructeur.

  • Constructeurs :
    • B n'hérite pas des constructeurs de A ;
    • Sauf si le ctor de B appelle explicitement l'un des le ctor de A, le ctor par défaut de A sera appelé automatiquement antes de le corps du ctor de B (l'idée étant que A doit être initialisé avant que B ne soit créé).
  • Destructeurs :
    • B n'hérite pas du dtor de A ;
    • Après il sort, le destructeur de B appellera automatiquement le destructeur de A.

Remerciements : Je tiens à remercier tout particulièrement Oli Charlesworth et Kos pour leurs réponses. J'ai choisi la réponse de Kos comme solution car c'est celle que j'ai le mieux comprise.


POSTE ORIGINAL

Lorsque vous recherchez "C++ destructor inheritance site:stackoverflow.com" sur Google, vous trouvez actuellement les messages suivants :

  1. Héritage des constructeurs et des destructeurs deux utilisateurs avec 30k+ de réputation disent que c'est hérité, et que ce n'est pas le cas
  2. Les destructeurs virtuels sont-ils hérités ? : ici, rien n'est mentionné qui indiquerait que les destructeurs ne sont pas hérités.
  3. Destructeurs et héritage en C++ ? : Les commentaires semblent indiquer que les destructeurs sont hérités.

Q1 : Ce que je sais également de la pratique, c'est que vous ne pouvez pas initialiser un objet dérivé avec le même prototype que le constructeur de son parent sans définir explicitement un constructeur pour la classe dérivée, est-ce correct ?


Même s'il est assez clair, d'après les messages, que les destructeurs semblent être hérités, je suis toujours perplexe par le fait qu'un utilisateur avec 32k de réputation dise le contraire. J'ai écrit un petit exemple qui devrait clarifier l'esprit de chacun :

#include <cstdio>

/******************************/

// Base class
struct A
{
    A() { printf( "\tInstance counter = %d (ctor)\n", ++instance_counter ); }
    ~A() { printf( "\tInstance counter = %d (dtor)\n", --instance_counter ); }

    static int instance_counter;
};

// Inherited class with default ctor/dtor
class B : public A {};

// Inherited class with defined ctor/dtor
struct C : public A
{
    C() { printf("\tC says hi!\n"); }
    ~C() { printf("\tC says bye!\n"); }
};

/******************************/

// Initialize counter
int A::instance_counter = 0;

/******************************/

// A few tests
int main()
{
    printf("Create A\n"); A a;
    printf("Delete A\n"); a.~A();

    printf("Create B\n"); B b;
    printf("Delete B\n"); b.~B();

    printf("Create new B stored as A*\n"); A *a_ptr = new B();
    printf("Delete previous pointer\n"); delete a_ptr;

    printf("Create C\n"); C c;
    printf("Delete C\n"); c.~C();

}

et voici le résultat (compilé avec g++ 4.4.3) :

Create A
    Instance counter = 1 (ctor)
Delete A
    Instance counter = 0 (dtor)
Create B
    Instance counter = 1 (ctor)
Delete B
    Instance counter = 0 (dtor)
Create new B stored as A*
    Instance counter = 1 (ctor)
Delete previous pointer
    Instance counter = 0 (dtor)
Create C
    Instance counter = 1 (ctor)
    C says hi!
Delete C
    C says bye!
    Instance counter = 0 (dtor)  // We exit main() now
    C says bye! 
    Instance counter = -1 (dtor)
    Instance counter = -2 (dtor)
    Instance counter = -3 (dtor)

Q2 : Ceux qui pensent que ce n'est pas un héritage peuvent-ils l'expliquer ?

Q3 : Alors que se passe-t-il lorsque vous appelez le constructeur d'une sous-classe avec des entrées ? Le "constructeur vide" de la superclasse est-il également appelé ?

41voto

Kos Points 29125

Terminologie, terminologie...

OK, qu'est-ce qu'on entend par "Foo est hérité" ? Nous voulons dire que si les objets de la classe A tienen Foo dans son interface, alors les objets de la classe B qui est une sous-classe de A ont également Foo dans son interface.

  • Constructeurs ne font pas partie de l'interface des objets. Ils appartiennent directement aux classes. Classes A y B peuvent fournir des ensembles de constructeurs complètement différents. Il n'y a pas d'"héritage" ici.

    ( Détail de l'implémentation : chaque constructeur de B appelle un constructeur de A. )

  • Destructeurs font en effet partie de l'interface de chaque objet, puisque c'est l'utilisateur de l'objet qui est chargé de les appeler (c'est-à-dire directement avec la commande delete ou indirectement en laissant un objet hors de portée). Chaque objet a exactement un destructeur Il s'agit de son propre destructeur, qui peut éventuellement être un destructeur virtuel. Il est toujours le sien, et il n'est pas hérité.

    (Détail de l'implémentation : le destructeur de B appelle le destructeur de A).

Ainsi, il y a un lien entre les constructeurs et les destructeurs de la base et des dérivés, mais ce n'est pas comme s'ils étaient hérités.

J'espère que cela répond à ce que vous avez en tête.

0 votes

Commentaire 1 : Je ne comprends pas votre première phrase, ne serait-ce pas "alors les objets de la classe B qui est une sous-classe de A ont aussi Foo dans leur interface" ?

0 votes

Commentaire 2 : Donc, comme pour les deux commentaires de Oli Charlesworth et Pete Becker, il semble que ce soit une question de terminologie. Il n'est pas hérité, mais il appelle automatiquement quelque chose de la superclasse, c'est ça ?

0 votes

@Sh3ljohn Commentaire 1 : Bien sûr que vous avez raison, je vais modifier et corriger. Commentaire 2 : Oui, "pas hérité" comme dans "pas dans l'interface publique des objets dérivés", contrairement aux autres membres publics de la classe (méthodes et champs).

7voto

Oli Charlesworth Points 148744

Q1 : Ce que je sais aussi par la pratique, c'est que vous ne pouvez pas initialiser un objet dérivé avec le même prototype que le constructeur de son parent sans définir explicitement un constructeur pour la classe dérivée, est-ce correct ?

En dehors du cas trivial où vous avez défini un constructeur par défaut dans la superclasse, oui, vous avez raison.


Q2 : Ceux qui pensent que ce n'est pas un héritage peuvent-ils l'expliquer ?

Il s'agit peut-être d'une question de définition de la terminologie. Alors qu'il est clair que les destructeurs virtuels existent et fonctionnent "comme prévu", nous voyons dans la norme C++ ([class.virtual]) :

Même si les destructeurs ne sont pas hérités un destructeur dans une classe dérivée remplace le destructeur de la classe de base déclaré virtuel.

(c'est moi qui souligne)


Q3 : Que se passe-t-il donc lorsque vous appelez le constructeur d'une sous-classe avec des entrées ? Le "constructeur vide" de la superclasse est-il également appelé ?

Si vous n'invoquez pas explicitement un constructeur de superclasse spécifique, alors le constructeur de superclasse par défaut sera appelé (en supposant qu'il soit visible).

0 votes

Merci :) Alors, pour Q2, qu'est-ce que cela signifie exactement ? D'après ce que je comprends de la citation ; les destructeurs ne sont pas hérités, ce qui signifie que la suppression de B ne devrait pas décrémenter le compteur.

0 votes

@Sh3ljohn : Qu'est-ce que vous demandez, spécifiquement, si "ce n'est pas censé être comme ça" ?

0 votes

Désolé, je modifiais mon commentaire lorsque vous avez posté votre réponse, cela devrait être plus clair.

4voto

Pete Becker Points 27371

Les destructeurs sont pas hérité. Si une classe n'en définit pas, le compilateur génère un. Pour les cas triviaux, ce destructeur appelle simplement le destructeur de la classe de base, et souvent cela signifie qu'il n'y a pas de code explicite pour son destructeur (ce qui imite l'héritage). Mais si une classe possède des membres avec des destructeurs, le destructeur généré appelle les destructeurs de ces membres avant d'appeler le destructeur de la classe de base. C'est quelque chose qu'une fonction héritée ne ferait pas.

0 votes

Donc tout est dans le mot "héritage", n'est-ce pas ? Parce que le destructeur de C appelle toujours le destructeur de A dans l'exemple.

1 votes

Si C::f() appelle A::f() il n'est pas vrai que C hérite de A::f .

0 votes

Hmm. Alors pourquoi un exemple dans la norme utilise, et g++ permet, d.B::~B(); ?

4voto

sourcecode Points 1196

L'héritage est ce qui : mécanisme permettant de réutiliser et d'étendre des classes existantes sans les modifier, produisant ainsi des relations hiérarchiques entre elles.

Héritage est presque comme l'intégration d'un objet dans une classe.

quand une classe hérite d'une classe de base, le constructeur de la classe de base est alors appelé d'abord, puis celle de la classe dérivée, et celle du destructeur. appelez est dans l'ordre inverse.

Alors pourquoi le constructeur de la classe de base est appelé (appelé sans être hérité, peut-être avec des paramètres/par défaut) : pour garantir que la classe de base est correctement construite lorsque le constructeur de la classe dérivée est exécuté.

Appel du destructeur (appel non hérité) : lorsque l'objet de base sort de la portée, le destructeur est appelé de lui-même. il n'y a donc pas de problème d'héritage du destructeur.

maintenant vos questions :

ans 1 - oui, vous avez raison pour la première question.
ans 2 - ainsi le destructeur est appelé non hérité après que la portée de l'objet soit sortie.
& et 3 - Si dans la classe dérivée vous donnez l'appel avec des paramètres alors seulement ce constructeur sera appelé, avec lui aucun autre constructeur ne sera appelé.
il n'y a aucun intérêt à ce que 2 constructeurs du même objet soient appelés lors de la création de l'objet, car constructeur appelé à la création d'un objet. Il prépare le nouvel objet à être utilisé. Il n'y a donc aucune logique à préparer l'objet deux fois avec des constructeurs différents.

0 votes

Que vous n'avez pas mentionné pour les constructeurs dans ans2 . pouvez-vous également nous en parler ?

4voto

aschepler Points 23731

Techniquement, les destructeurs sont hérités. Mais dans des circonstances normales, les destructeurs hérités ne sont pas directement utilisés pour une classe dérivée ; ils sont invoqués parce que le propre destructeur de la classe dérivée les appelle afin de détruire ses propres "sous-objets de la classe de base" comme une étape dans la destruction de l'objet plus grand. Et dans les circonstances inhabituelles où vous utilisez directement un destructeur de classe de base sur un objet dérivé, il est très difficile d'éviter un comportement indéfini.

Cet exemple provient directement de la norme C++ (12.4p12).

struct B {
  virtual ~B() { }
};
struct D : B {
  ~D() { }
};

D D_object;
typedef B B_alias;
B* B_ptr = &D_object;

void f() {
  D_object.B::~B();              // calls B's destructor
  B_ptr->~B();                   // calls D's destructor
  B_ptr->~B_alias();             // calls D's destructor
  B_ptr->B_alias::~B();          // calls B's destructor
  B_ptr->B_alias::~B_alias();    // calls B's destructor
}

Si ~B n'étaient pas un membre hérité de D la première déclaration dans f serait mal formé. Comme c'est le cas, c'est légal C++, mais extrêmement dangereux.

0 votes

Et si tu faisais ~B() non-virtuel ? L'un des exemples ci-dessus échouerait-il ?

0 votes

Je pense qu'elles seraient toutes bien formées, mais qu'elles font toutes la même chose (appeler B et non pas le destructeur de D 's).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X