web-dev-qa-db-fra.com

Quelles sont les règles pour appeler le constructeur de la superclasse?

Quelles sont les règles C++ pour appeler le constructeur de la super-classe à partir d'une sous-classe?

Par exemple, je sais qu'en Java, vous devez le faire en tant que première ligne du constructeur de la sous-classe (et si vous ne le faites pas, un appel implicite à un super constructeur sans argument est supposé - ce qui vous donne une erreur de compilation en cas d'absence). .

639
levik

Les constructeurs de classe de base sont automatiquement appelés pour vous s'ils n'ont pas d'argument. Si vous souhaitez appeler un constructeur de superclasse avec un argument, vous devez utiliser la liste d'initialisation du constructeur de la sous-classe. Contrairement à Java, C++ prend en charge plusieurs héritages (pour le meilleur ou pour le pire), de sorte que la classe de base doit être référencée par son nom plutôt que par "super ()".

class SuperClass
{
    public:

        SuperClass(int foo)
        {
            // do something with foo
        }
};

class SubClass : public SuperClass
{
    public:

        SubClass(int foo, int bar)
        : SuperClass(foo)    // Call the superclass constructor in the subclass' initialization list.
        {
            // do something with bar
        }
};

Plus d'infos sur la liste d'initialisation du constructeur ici et ici .

865
luke

En C++, les constructeurs sans argument de toutes les superclasses et de toutes les variables membres sont appelés pour vous avant d'entrer dans votre constructeur. Si vous voulez leur transmettre des arguments, il existe une syntaxe distincte pour cette opération appelée "chaîne de constructeur", qui ressemble à ceci:

class Sub : public Base
{
  Sub(int x, int y)
  : Base(x), member(y)
  {
  }
  Type member;
};

Si quelque chose est exécuté à ce stade, les destructeurs sont appelés pour les bases/membres qui ont déjà terminé la construction et l'exception est renvoyée à l'appelant. Si vous voulez intercepter des exceptions lors du chaînage, vous devez utiliser une fonction try block:

class Sub : public Base
{
  Sub(int x, int y)
  try : Base(x), member(y)
  {
    // function body goes here
  } catch(const ExceptionType &e) {
    throw kaboom();
  }
  Type member;
};

Dans cette forme, notez que le bloc try is le corps de la fonction, plutôt que d'être à l'intérieur du corps de la fonction; Cela lui permet de détecter les exceptions générées par les initialisations implicites ou explicites des membres et des classes de base, ainsi que pendant le corps de la fonction. Cependant, si un bloc d'interception de fonction ne déclenche pas une exception différente, le moteur d'exécution rediffuse l'erreur d'origine. les exceptions lors de l'initialisation ne peut pas être ignorées.

223
puetzk

En C++, il existe un concept de liste d'initialisation de constructeur, qui vous permet d'appeler le constructeur de la classe de base et d'initialiser les données membres. La liste d'initialisation vient après la signature du constructeur après un signe deux-points et avant le corps du constructeur. Disons que nous avons une classe A:


class A : public B
{
public:
  A(int a, int b, int c);
private:
  int b_, c_;
};

Ensuite, en supposant que B ait un constructeur qui prend un int, le constructeur de A peut ressembler à ceci:


A::A(int a, int b, int c) 
  : B(a), b_(b), c_(c) // initialization list
{
  // do something
}

Comme vous pouvez le constater, le constructeur de la classe de base est appelé dans la liste d’initialisation. Au fait, il est préférable d’initialiser les membres de la liste d’initialisation plutôt que d’affecter les valeurs de b_, et c_ dans le corps du constructeur, car vous économisez le coût supplémentaire de l’attribution.

N'oubliez pas que les membres de données sont toujours initialisés dans l'ordre dans lequel ils ont été déclarés dans la définition de la classe, quel que soit leur ordre dans la liste d'initialisation. Pour éviter les bugs étranges, qui peuvent survenir si vos membres de données dépendent les uns des autres, vous devez toujours vous assurer que l'ordre des membres est le même dans la liste d'initialisation et dans la définition de la classe. Pour la même raison, le constructeur de la classe de base doit être le premier élément de la liste d'initialisation. Si vous l'omettez complètement, le constructeur par défaut de la classe de base sera appelé automatiquement. Dans ce cas, si la classe de base n'a pas de constructeur par défaut, vous obtiendrez une erreur du compilateur.

50
Dima

Tout le monde a mentionné un appel de constructeur via une liste d'initialisation, mais personne n'a dit que le constructeur d'une classe parente puisse être appelé explicitement à partir du corps du constructeur du membre dérivé. Voir la question Appel d'un constructeur de la classe de base à partir du corps du constructeur d'une sous-classe , par exemple. Le fait est que si vous utilisez un appel explicite à un constructeur de classe parent ou de super-classe dans le corps d'une classe dérivée, il ne s'agit en fait que de créer une instance de la classe parent et ne pas appeler le constructeur de la classe parent sur l'objet dérivé. . Le seul moyen d'appeler un constructeur de classe parent ou de super classe sur un objet de classe dérivée consiste à utiliser la liste d'initialisation et non dans le corps du constructeur de classe dérivée. Alors peut-être que cela ne devrait pas être appelé un "appel de constructeur de super-classe". Je mets cette réponse ici parce que quelqu'un pourrait être confus (comme je l'ai fait).

21
TT_

Si vous avez un constructeur sans arguments, il sera appelé avant que le constructeur de la classe dérivée soit exécuté.

Si vous voulez appeler un constructeur de base avec des arguments, vous devez l'écrire explicitement dans le constructeur dérivé comme ceci:

class base
{
  public:
  base (int arg)
  {
  }
};

class derived : public base
{
  public:
  derived () : base (number)
  {
  }
};

Vous ne pouvez pas construire une classe dérivée sans appeler le constructeur de parents en C++. Cela se produit automatiquement s'il s'agit d'un C'tor sans argument, cela se produit si vous appelez directement le constructeur dérivé comme indiqué ci-dessus ou si votre code ne sera pas compilé.

19
Nils Pipenbrinck

Le seul moyen de transmettre des valeurs à un constructeur parent consiste à utiliser une liste d'initialisation. La liste d'initialisation est implémentée avec a: puis une liste de classes et les valeurs à transmettre au constructeur de cette classe.

Class2::Class2(string id) : Class1(id) {
....
}

Rappelez-vous également que si vous avez un constructeur qui ne prend aucun paramètre sur la classe parente, il sera appelé automatiquement avant l'exécution du constructeur enfant.

19
CR.

Si vous avez des paramètres par défaut dans votre constructeur de base, la classe de base sera appelée automatiquement.

using namespace std;

class Base
{
    public:
    Base(int a=1) : _a(a) {}

    protected:
    int _a;
};

class Derived : public Base
{
  public:
  Derived() {}

  void printit() { cout << _a << endl; }
};

int main()
{
   Derived d;
   d.printit();
   return 0;
}

La sortie est: 1

11
edW
CDerived::CDerived()
: CBase(...), iCount(0)  //this is the initialisation list. You can initialise member variables here too. (e.g. iCount := 0)
    {
    //construct body
    }
9
Dynite

Personne n'a mentionné la séquence d'appels de constructeur lorsqu'une classe dérive de plusieurs classes. La séquence est telle que mentionnée tout en dérivant les classes.

7
krishna_oza