web-dev-qa-db-fra.com

Ordre d'appel des constructeurs / destructeurs dans l'héritage

Une petite question sur la création d'objets. Disons que j'ai 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 en général je crée une instance de B:

int main(){
    B b;
}

Notez que B dérive de A et a également un champ de type A.

J'essaie de comprendre les règles. Je sais que lors de 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 appellera-t-il le constructeur de A? Je n'ai pas défini de liste d'initialisation, existe-t-il une sorte de liste par défaut? Et s'il n'y a pas de liste par défaut? Et la même question sur la destruction.

38
Bug
  • La construction commence toujours par la base class. S'il existe plusieurs bases classes, la construction commence par la base la plus à gauche. (note latérale: S'il y a un héritage virtual alors il est donné une préférence plus élevée).
  • Ensuite, les champs membres sont construits. Ils sont initialisés dans l'ordre où ils sont déclarés
  • Enfin, le class lui-même est construit
  • L'ordre du destructeur est exactement l'inverse

Quelle que soit la liste des initialiseurs, l'ordre des appels sera le suivant:

  1. Constructeur de base de class A
  2. Le champ de class B Nommé a (de type class A) Sera construit
  3. Constructeur dérivé de class B
75
iammilind

En supposant qu'il n'y a pas d'héritage virtuel/multiple (ce qui complique un peu les choses), les règles sont simples:

  1. La mémoire objet est allouée
  2. Le constructeur des classes de base est exécuté, se terminant par le plus dérivé
  3. L'initialisation du membre est exécutée
  4. L'objet devient une véritable instance de sa classe
  5. Le code 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, car il ne gagne ce titre qu'après le début de l'exécution du constructeur. Cela signifie que s'il y a une exception levée pendant le constructeur d'un membre, le destructeur de l'objet n'est pas exécuté, mais seules les parties déjà construites (par exemple les membres ou les classes de base) seront détruites. Cela signifie également que si dans le constructeur d'un membre ou d'une classe de base vous appelez une fonction membre virtuelle de l'objet, l'implémentation appelée sera celle de base, pas celle dérivée. Une autre chose importante à retenir est que les membres répertoriés dans la liste d'initialisation seront construits dans l'ordre dans lequel ils sont déclarés dans la classe, PAS dans l'ordre dans lequel ils apparaissent dans la liste d'initialisation (heureusement, la plupart des compilateurs décents émettront un avertissement si vous listez les membres dans un ordre différent de la déclaration de classe).

Notez également que même si pendant l'exécution du code constructeur l'objet this a déjà gagné sa classe finale (par exemple en ce qui concerne la répartition virtuelle) le destructeur de la classe ne sera PAS appelé à moins que le constructeur termine son exécution. Ce n'est que lorsque le constructeur a terminé l'exécution que l'instance d'objet est un véritable citoyen de première classe parmi les instances ... avant que ce point ne soit qu'une "instance de volonté" (malgré la classe correcte).

La destruction se produit dans l'ordre inverse exact: d'abord le destructeur d'objet est exécuté, puis il perd sa classe (c'est-à-dire qu'à partir de ce moment, l'objet est considéré comme un objet de base) puis tous les membres sont détruits dans l'ordre de déclaration inverse et enfin le processus de destruction de la classe de base est exécuté jusqu'au parent le plus abstrait. En ce qui concerne le constructeur, si vous appelez une fonction membre virtuelle de l'objet (directement ou indirectement) dans un destructeur de base ou de membre, l'implémentation exécutée sera celle parent car l'objet a perdu son titre de classe lorsque le destructeur de classe s'est terminé.

22
6502

Les classes de base sont toujours construites avant les membres de données. Les membres de données sont construits dans l'ordre dans lequel ils sont déclarés dans la classe. Cet ordre n'a rien à voir avec la liste d'initialisation. Lorsqu'un membre de données est en cours d'initialisation, il recherchera les paramètres dans votre liste d'initialisation et appellera le constructeur par défaut s'il n'y a pas de correspondance. Les destructeurs des membres de données sont toujours appelés dans l'ordre inverse.

7
Vaughn Cato

Le constructeur de la classe de base s'exécute toujours en premier.so lorsque vous écrivez une instruction B b; le constructeur de A est appelé en premier, puis la classe B constructor.therefore la sortie des constructeurs sera dans une séquence comme suit:

A() C-tor
A() C-tor
B() C-tor
3
shubhendu mahajan
#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 les constructeurs sont appelés ici:

  1. B :: Un constructeur (int n = 2) est appelé
  2. Le constructeur B :: B (5) est appelé
  3. B.m_A1 :: A (3) est appelé
  4. B.m_A2 :: A (5) est appelé

Le destructeur de même manière est appelé:

  1. B :: ~ B () est appelé. c'est-à-dire m_i = 2, qui décrémente m_i à 1 dans A.
  2. B.m_A2 :: ~ A () est appelé. m_i = 5
  3. 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 est sans importance pour l'ordre de l'ordre de liste d'initialisation mais pour leur ordre de déclaration.

1
NEERAJ SWARNKAR

La sortie du code modifié est:

A() C-tor
A() C-tor
B() C-tor
~B() D-tor
~A() D-tor
~A() D-tor
0
Caleb