Ordre d'appel des constructeurs/destructeurs en héritage
une petite question sur la création d'objets. Dire que j'ai de ces deux classes:
struct A{
A(){cout << "A() C-tor" << endl;}
~A(){cout << "~A() D-tor" << endl;}
};
struct B : public A{
B(){cout << "B() C-tor" << endl;}
~B(){cout << "~B() D-tor" << endl;}
A a;
};
et dans le principal je crée une instance de B
:
int main(){
B b;
}
Notez que B
dérive de A
et aussi un champ de type A
.
j'essaie de comprendre les règles. Je sais que lorsque la construction d'un objet appelle d'abord son constructeur parent, et vice versa lors de la destruction.
Qu'en est-il des champs (A a;
dans ce cas)? Lorsque B
est créé, quand il call A
'constructeur? Je n'ai pas défini une liste d'initialisation, est-il une sorte de une liste par défaut? Et si il n'y a pas de liste par défaut? Et la même question sur la destruction.
6 réponses
- la Construction commence toujours par la base
class
. S'il y a plusieurs baseclass
es alors, la construction commence avec la base la plus à gauche. (note: S'il y a unvirtual
héritage alors il est donné la préférence plus élevée). - puis les champs membres sont construits. Ils sont initialisés dans le ordonnez qu'ils soient déclarés!--13-->
- Enfin, le
class
lui-même est construit - l'ordre du destructeur est exactement inverse
Indépendamment de l'initialiseur de liste, l'ordre d'appel sera comme ceci:
- Base
class A
constructeur class B
'champ nomméa
(de typeclass A
) seront construits- dérivée
class B
constructeur
en supposant qu'il n'y ait pas d'héritage virtuel/multiple (ce qui complique les choses un peu) alors les règles sont simples:
- la mémoire de l'objet est attribuée
- le constructeur des classes de base est exécuté, se terminant par les plus dérivées
- l'initialisation du membre est exécutée
- L'objet devient une véritable instance de sa classe
- le code du Constructeur est exécuté
Une chose importante à retenir est que jusqu'à l'étape 4, l'objet n'est pas encore une instance de sa classe, becuse il gagne ce titre qu'après l'exécution du constructeur commence. Cela signifie que s'il y a une exception lancée pendant le constructeur d'un membre, le destructeur de l'objet n'est pas exécuté, mais seules les pièces déjà construites (par exemple les membres ou les classes de base) seront détruites. Cela signifie aussi que si dans le constructeur d'un membre ou d'une classe de base vous appelez n'importe quelle fonction de membre virtuel de l'objet l'implémentation appelée sera celle de base, pas la dérivée. Une autre chose importante à retenir est que les membres listés dans la liste d'initialisation seront construits dans l'ordre où ils sont déclarés dans la classe, et non dans l'ordre où ils apparaissent dans la liste d'initialisation (heureusement la plupart des compilateurs décents émettront un avertissement si vous énumérez des membres dans un ordre différent de celui de la déclaration de classe).
Notez aussi que même si pendant l'exécution du code du constructeur le this
objet déjà gagné sa classe finale (par exemple en ce qui concerne virtual dispatch) le destructeur de la classe ne va pas être appelé à moins que le constructeur achève son exécution. Ce n'est que lorsque le constructeur termine l'exécution que l'instance de l'objet est un véritable citoyen de première classe parmi les instances... avant ce point est seulement une instance "wanna-be" (en dépit d'avoir la classe correcte).
la Destruction se produit dans l'ordre exact inverse: d'abord le destructeur d'objet est exécuté, puis il perd sa classe (i.e. à partir de ce point sur l'objet est considéré comme un objet de base) alors tous les membres sont détruits dans l'ordre inverse de la déclaration et finalement le processus de destruction de classe de base est exécuté jusqu'au parent le plus abstrait. Comme pour le constructeur si vous appelez n'importe quelle fonction de membre virtuel de l'objet (soit directement ou indirectement) dans une base ou un destructeur de membre l'implémentation exécutée sera la parent parce que l'objet a perdu son titre de classe quand la classe destructor compléter.
les classes de Base sont toujours construites avant les membres de données. Les membres de données sont construits dans l'ordre où ils sont déclarés dans la classe. Cet ordre n'a rien à voir avec la liste d'initialisation. Quand un membre de données est initialisé, il va regarder dans votre liste d'initialisation pour les paramètres, et appeler le constructeur par défaut s'il n'y a pas de correspondance. Les destructeurs pour les données membres sont toujours appelés dans l'ordre inverse.
le constructeur de classe de Base exécute toujours en premier.donc, quand vous écrivez une déclaration B b;
le constructeur de A
s'appelle d'abord et ensuite le B
le constructeur de la classe.par conséquent, la sortie de l'constructeurs sera dans une séquence comme suit:
A() C-tor
A() C-tor
B() C-tor
#include<iostream>
class A
{
public:
A(int n=2): m_i(n)
{
// std::cout<<"Base Constructed with m_i "<<m_i<<std::endl;
}
~A()
{
// std::cout<<"Base Destructed with m_i"<<m_i<<std::endl;
std::cout<<m_i;
}
protected:
int m_i;
};
class B: public A
{
public:
B(int n ): m_a1(m_i + 1), m_a2(n)
{
//std::cout<<"Derived Constructed with m_i "<<m_i<<std::endl;
}
~B()
{
// std::cout<<"Derived Destructed with m_i"<<m_i<<std::endl;
std::cout<<m_i;//2
--m_i;
}
private:
A m_a1;//3
A m_a2;//5
};
int main()
{
{ B b(5);}
std::cout <<std::endl;
return 0;
}
La réponse dans ce cas est 2531. Comment le constructeur sont appelés ici:
- B::A(int n=2) constructeur est appelé
- B::B(5) Le constructeur est appelé
- B. m_A1:: A (3) s'appelle
- B. m_A2:: A (5) s'appelle
de La même façon Destructeur est appelé:
- B::~B() s'appelle. I. e m_i = 2, qui décrément m_i à 1 dans A.
- B. m_A2::~a() s'appelle. m_i = 5
- B. m_A1::~A() est appelé. m_i = 3 4 B::~A () est appelé., m_i = 1
dans cet exemple, la construction de m_A1 & m_A2 n'est pas pertinente de l'ordre de liste d'initialisation mais de leur ordre de déclaration.
sortie du code modifié est:
A() C-tor
A() C-tor
B() C-tor
~B() D-tor
~A() D-tor
~A() D-tor