C++ héritage constructeur/destructeur
modifier: résumé des réponses
dans la suite, B est une sous-classe de A.
c'est une question de terminologie; ctors et dtors sont et non hérités, en ce sens que le ctor/dtor de B et non sera emprunté à l'interface de A. Une classe a au moins un constructeur, et a exactement un destructeur.
- constructeurs :
- B does not Inheritors from a;
- sauf si b's ctor appelle explicitement l'un des ctor de A, le ctor par défaut de A sera appelé automatiquement avant corps de ctor de B (l'idée étant que A doit être initialisé avant que B soit créé).
- destructeurs :
- B does n'hérite pas de son titre;
- après , le destructeur de B appellera automatiquement le destructeur de A.
Remerciements: Je voudrais remercier particulièrement Oli Charlesworth et Kos pour leurs réponses, j'ai placé la réponse de Kos comme la solution parce que c'était celle que j'ai le mieux compris.
L'ORIGINAL POST
Lors de la recherche pour "destructeur C++ héritage site:stackoverflow.com" sur Google, vous trouver actuellement les postes suivants:
- héritage constructeur et destructeur : deux utilisateurs de 30k + réputation dire qu'il est hérité, et qu'il n'est pas
- Les Destructeurs virtuels sont-ils hérités? : ici, rien n'est mentionné que serait destructeurs Non hérités
- Destructeurs et de l'héritage en C++? : les commentaires semblent indiquer que les destructeurs sont hérités
Q1: ce que je sais aussi de la pratique, c'est que vous ne pouvez pas initialiser un objet dérivé avec le même prototype que son constructeur 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 une réputation 32k dirait son pas. 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 An"); A a;
printf("Delete An"); a.~A();
printf("Create Bn"); B b;
printf("Delete Bn"); b.~B();
printf("Create new B stored as A*n"); A *a_ptr = new B();
printf("Delete previous pointern"); delete a_ptr;
printf("Create Cn"); C c;
printf("Delete Cn"); c.~C();
}
et voici la sortie (compilée 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: est-ce que quelqu'un qui pense qu'il n'est pas hérité s'il vous plaît expliquer cela?
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 aussi appelé?
6 réponses
terminologie, terminologie...
OK, qu'entend-on par"Foo est hérité"? Nous voulons dire que si les objets de la classe A
ont Foo
dans son interface, alors les objets de la classe B
qui est une sous-classe de A
ont aussi Foo
dans son interface.
-
les constructeurs ne font pas partie de l'interface des objets. Ils appartiennent directement aux classes. Classes
A
etB
peut fournir des ensembles complètement différents de constructeurs. Pas de "hérités" ici.( détail de la mise en œuvre: chaque constructeur de B appelle un constructeur de A. )
-
destructeurs sont en effet une partie de l'interface de chaque objet, puisque l'utilisateur de l'objet est responsable de les appeler (i.e. directement avec
delete
ou indirectement en laissant un objet sortir de portée). chaque objet a exactement un destructeur : son propre destructeur, qui pourrait éventuellement être virtuel. Il est toujours propre, et il n'est pas héréditaire.(détail de la mise en œuvre: le destructeur de B appelle le destructeur de A.)
ainsi: il y a une connexion entre la base et les constructeurs et destructeurs dérivés, mais ce n'est pas comme "ils sont hérités".
j'espère que cela répond à ce que vous avez en tête.
Q1: ce que je sais aussi de la pratique, c'est que vous ne pouvez pas initialiser un objet dérivé avec le même prototype que son constructeur parent sans définir explicitement un constructeur pour la classe dérivée, est-ce correct?
mis à part le cas trivial où vous avez défini un constructeur par défaut dans la superclasse, oui vous avez raison.
Q2: est-ce que quelqu'un qui pense que ce n'est pas héréditaire peut expliquer cela?
il peut s'agir de définitions de termes. Bien qu'il soit clair que les destructeurs virtuels existent et fonctionnent "comme prévu", nous voyons dans la norme C++ ([class.virtuel]):
même si les destructeurs ne sont pas hérités , un destructeur dans une classe dérivée l'emporte sur un destructeur de classe de base déclaré virtuel
(l'emphase est mienne)
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 aussi appelé?
si vous n'invoquez pas explicitement un constructeur de superclasses spécifique, alors le constructeur de superclasses par défaut sera appelé (en supposant qu'il soit visible).
destructeurs sont pas hérité. Si une classe n'en définit pas une, le compilateur génère . Pour les cas triviaux que destructor appelle simplement la classe de base' destructor, et souvent cela signifie qu'il n'y a pas de code explicite pour son destructeur (qui imite l'héritage). Mais si une classe a des membres avec des destructeurs, le destructeur généré appelle des destructeurs pour ces membres avant d'appeler la classe de base' destructeur. C'est quelque chose qu'une fonction héritée ne suffirait pas.
L'héritage est ce que: mécanisme de réutilisation et d'extension des classes existantes sans les modifier, produisant ainsi des relations hiérarchiques entre elles.
héritage est presque comme enchâsser un objet dans une classe.
quand la classe hérite d'une classe de base alors le constructeur de la classe de base est appelé d'abord la classe dérivée ,et la classe du destructeur appel est dans l'ordre inverse.
ainsi Pourquoi le constructeur de classe de Base est appelé (appelé non hérité peut être avec paramètres/défaut): pour garantir que la classe de base est correctement construite lorsque le constructeur pour la classe dérivée est exécuté.
maintenant appel de destructeur (ne pas hériter): lorsque l'objet de base sort de la portée, alors le destructeur est appelé de lui-même.donc il y a un problème d'héritage de destructeur.
maintenant vos questions:
ans 1 - oui vous avez raison pour la première question.
ans 2 - donc destructeur est appelé non hérité après la portée de l'objet sort.
& ans 3 - si dans la classe dérivée, vous donnez à l'appel avec des paramètres alors seulement qu' constructeur appelait , avec aucun autre constructeur appelait.
il n'y a aucun point d'issuse que 2 constructeurs d'un même objet seraient appelés sur la création d'objet, comme
constructeur appelé à la création d'un objet. Il prépare le nouvel objet pour son utilisation.il n'est donc pas logique de préparer l'objet deux fois avec des constructeurs différents.
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 non défini.
cet exemple vient directement du Standard 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'était pas un membre hérité de D
, la première déclaration dans f
serait mal formée. Comme il est, il est légal C++, bien que extrêmement dangereux.
dans votre exemple, vous appelez explicitement les fonctions de destructeur. C'est légal (évidemment, puisqu'il compilé et exécuté), mais presque toujours incorrecte.
pour les objets alloués dynamiquement créés avec new
, le destructeur sera lancé lorsque l'objet objecté est enlevé avec delete
.
pour les objets statiquement alloués, qui sont créés simplement en déclarant l'objet dans le cadre d'une fonction, le destructeur est exécuté lorsque l'objet du champ d'application disparaît. C'est-à-dire que lorsque main()
sort, les destructeurs des objets seront lancés. Mais vous avez déjà lancé les destructeurs pour ces objets en les appelant manuellement! C'est pourquoi la sortie de votre exemple montre que le nombre diminue à -3... vous avez lancé les destructeurs pour a
, b
, et c
deux fois.
voici le même code, annoté pour indiquer quand les destructeurs seront lancés automatiquement:
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; // Implicitly calls destructor for a_ptr. a_ptr is class B,
// so it would call a_ptr->~B() if it existed. Because B is an A, after
// its destructor is called, it calls the superclass's destructor,
// a_ptr->~A().
printf("Create C\n"); C c;
printf("Delete C\n"); c.~C();
}
// Function exits here at the close brace, so anything declared in its scope is
// deallocated from the stack and their destructors run.
// First `c` is destroyed, which calls c.~C(), then because C is a subclass of A
// calls c.~B() (which doesn't exist, so a blank implementation is used), then
// because B is a subclass of A calls c.~A(). This decrements the counter, but
// the count is wrong because you already manually called c.~C(), which you
// ordinarily shouldn't have done.
// Then `b` is destroyed, in a similar manner. Now the count is off by 2,
// because you had already called b.~B().
// Lastly `a` is destroyed, just as above. And again, because you had already
// called a.~A(), the count is now off by 3.