web-dev-qa-db-fra.com

Puis-je appeler un constructeur d'un autre constructeur (faire le chaînage de constructeur) en C ++?

En tant que développeur C # , je suis habitué à exécuter des constructeurs:

class Test {
    public Test() {
        DoSomething();
    }

    public Test(int count) : this() {
        DoSomethingWithCount(count);
    }

    public Test(int count, string name) : this(count) {
        DoSomethingWithName(name);
    }
}

Y a-t-il un moyen de faire cela en C++?

J'ai essayé d'appeler le nom de la classe et d'utiliser le mot clé 'this', mais les deux échouent.

856
Stormenet

C++ 11: oui!

C++ 11 et les versions ultérieures ont cette même fonctionnalité (appelée constructeurs délégués ).

La syntaxe est légèrement différente de C #:

class Foo {
public: 
  Foo(char x, int y) {}
  Foo(int y) : Foo('a', y) {}
};

C++ 03: Non

Malheureusement, il n'y a aucun moyen de faire cela en C++ 03, mais il y a deux façons de simuler ceci:

  1. Vous pouvez combiner deux constructeurs (ou plus) via des paramètres par défaut:

    class Foo {
    public:
      Foo(char x, int y=0);  // combines two constructors (char) and (char, int)
      // ...
    };
    
  2. Utilisez une méthode init pour partager le code commun:

    class Foo {
    public:
      Foo(char x);
      Foo(char x, int y);
      // ...
    private:
      void init(char x, int y);
    };
    
    Foo::Foo(char x)
    {
      init(x, int(x) + 7);
      // ...
    }
    
    Foo::Foo(char x, int y)
    {
      init(x, y);
      // ...
    }
    
    void Foo::init(char x, int y)
    {
      // ...
    }
    

Voir l'entrée de la FAQ C++ pour référence.

1148
JohnIdol

Non, vous ne pouvez pas appeler un constructeur d'un autre en C++ 03 (appelé constructeur délégué).

Cela a changé dans C++ 11 (alias C++ 0x), ce qui a ajouté le support de la syntaxe suivante:
(exemple tiré de Wikipedia )

class SomeType
{
  int number;

public:
  SomeType(int newNumber) : number(newNumber) {}
  SomeType() : SomeType(42) {}
};
108
Cyrille Ka

Je crois que vous pouvez appeler un constructeur à partir d'un constructeur. Il va compiler et exécuter. J'ai récemment vu quelqu'un faire cela et cela fonctionnait sous Windows et Linux.

Ça ne fait pas ce que tu veux. Le constructeur interne construira un objet local temporaire qui sera supprimé une fois que le constructeur externe sera retourné. Ils devraient également être des constructeurs différents ou vous créeriez un appel récursif.

Réf.: https://isocpp.org/wiki/faq/ctors#init-methods

39
ohlemacher

Il est à noter que vous pouvez appelez le constructeur d’une classe parente dans votre constructeur, par exemple:

class A { /* ... */ };

class B : public A
{
    B() : A()
    {
        // ...
    }
};

Mais non, vous ne pouvez pas appeler un autre constructeur de la même classe.

22
kchoose2

Dans C++ 11 , un le constructeur peut appeler une autre surcharge de constructeur :

class Foo  {
     int d;         
public:
    Foo  (int i) : d(i) {}
    Foo  () : Foo(42) {} //New to C++11
};

De plus, les membres peuvent aussi être initialisés comme ceci.

class Foo  {
     int d = 5;         
public:
    Foo  (int i) : d(i) {}
};

Cela devrait éliminer le besoin de créer la méthode d'assistance à l'initialisation. Et il est toujours recommandé de ne pas appeler de fonctions virtuelles dans les constructeurs ou les destructeurs afin d'éviter d'utiliser des membres qui pourraient ne pas être initialisés.

18
Ben L

Si vous voulez faire le mal, vous pouvez utiliser le "nouvel" opérateur sur place:

class Foo() {
    Foo() { /* default constructor deliciousness */ }
    Foo(Bar myParam) {
      new (this) Foo();
      /* bar your param all night long */
    } 
};

Semble travailler pour moi.

modifier

Comme @ElvedinHamzagic l'a fait remarquer, si Foo contenait un objet qui allouait de la mémoire, cet objet pourrait ne pas être libéré. Cela complique davantage les choses.

Un exemple plus général:

class Foo() {
private:
  std::vector<int> Stuff;
public:
    Foo()
      : Stuff(42)
    {
      /* default constructor deliciousness */
    }

    Foo(Bar myParam)
    {
      this->~Foo();
      new (this) Foo();
      /* bar your param all night long */
    } 
};

Semble un peu moins élégant, à coup sûr. @ La solution de JohnIdol est bien meilleure.

12
lyngvi

Non, en C++, vous ne pouvez pas appeler un constructeur à partir d'un constructeur. Comme l'a souligné warren, vous pouvez faire ce qui suit:

  • Surcharger le constructeur, en utilisant différentes signatures
  • Utiliser les valeurs par défaut sur les arguments pour rendre une version "plus simple" disponible

Notez que dans le premier cas, vous ne pouvez pas réduire la duplication de code en appelant un constructeur à partir d'un autre. Vous pouvez bien sûr avoir une méthode séparée, privée/protégée, qui effectue toute l’initialisation, et laisse le constructeur s’occuper principalement de la gestion des arguments.

8
unwind

Autrement dit, vous ne pouvez pas avant C++ 11.

C++ 11 introduit constructeurs délégués :

Constructeur délégant

Si le nom de la classe elle-même apparaît en tant que classe ou identificateur dans la liste d'initialisation de membre, la liste doit alors être composée de cet initialiseur de membre uniquement; ce constructeur est appelé constructeur délégué, et le constructeur sélectionné par le seul membre de la liste d'initialisation est le constructeur cible.

Dans ce cas, le constructeur cible est sélectionné par résolution de surcharge et exécuté en premier, puis le contrôle retourne au constructeur délégué et son corps est exécuté.

Les constructeurs délégués ne peuvent pas être récursifs.

class Foo {
public: 
  Foo(char x, int y) {}
  Foo(int y) : Foo('a', y) {} // Foo(int) delegates to Foo(char,int)
};

Notez qu'un constructeur délégué est une proposition tout-ou-rien; Si un constructeur délègue à un autre constructeur, le constructeur appelant n'est pas autorisé à avoir d'autres membres dans sa liste d'initialisation. Cela a du sens si vous envisagez d’initialiser les membres const/reference une fois et une seule fois.

4
user8683714

Dans Visual C++, vous pouvez également utiliser cette notation dans le constructeur: this-> Classname :: Classname (paramètres d'un autre constructeur). Voir un exemple ci-dessous:

class Vertex
{
 private:
  int x, y;
 public:
  Vertex(int xCoo, int yCoo): x(xCoo), y(yCoo) {}
  Vertex()
  {
   this->Vertex::Vertex(-1, -1);
  }
};

Je ne sais pas si cela fonctionne ailleurs, je ne l'ai testé que dans Visual C++ 2003 et 2008. Vous pouvez également appeler plusieurs constructeurs de cette façon, je suppose, comme dans Java et C#.

P.S .: Franchement, j'ai été surpris que cela n'ait pas été mentionné auparavant.

4
izogfif

Une autre option qui n’a pas encore été présentée consiste à scinder votre classe en deux, en encerclant une classe d’interface légère autour de votre classe d’origine afin d’obtenir l’effet recherché:

class Test_Base {
    public Test_Base() {
        DoSomething();
    }
};

class Test : public Test_Base {
    public Test() : Test_Base() {
    }

    public Test(int count) : Test_Base() {
        DoSomethingWithCount(count);
    }
};

Cela pourrait devenir compliqué si de nombreux constructeurs doivent appeler leur homologue "next level up", mais pour une poignée de constructeurs, cela devrait être réalisable.

3
e.James

Je proposerais l'utilisation d'une méthode private friend qui implémente la logique d'application du constructeur et qui est appelée par les différents constructeurs. Voici un exemple:

Supposons que nous ayons une classe appelée StreamArrayReader avec certains champs privés:

private:
    istream * in;
      // More private fields

Et nous voulons définir les deux constructeurs:

public:
    StreamArrayReader(istream * in_stream);
    StreamArrayReader(char * filepath);
    // More constructors...

Où le second utilise simplement le premier (et bien sûr, nous ne voulons pas dupliquer la mise en œuvre de l'ancien). Idéalement, on aimerait faire quelque chose comme:

StreamArrayReader::StreamArrayReader(istream * in_stream){
    // Implementation
}

StreamArrayReader::StreamArrayReader(char * filepath) {
    ifstream instream;
    instream.open(filepath);
    StreamArrayReader(&instream);
    instream.close();
}

Cependant, ceci n'est pas autorisé en C++. Pour cette raison, nous pouvons définir une méthode privée privée comme suit, qui implémente ce que le premier constructeur est censé faire:

private:
  friend void init_stream_array_reader(StreamArrayReader *o, istream * is);

Maintenant cette méthode (parce que c'est un ami) a accès aux champs privés de o. Ensuite, le premier constructeur devient:

StreamArrayReader::StreamArrayReader(istream * is) {
    init_stream_array_reader(this, is);
}

Notez que cela ne crée pas plusieurs copies pour les copies nouvellement créées. Le second devient:

StreamArrayReader::StreamArrayReader(char * filepath) {
    ifstream instream;
    instream.open(filepath);
    init_stream_array_reader(this, &instream);
    instream.close();
}

C’est-à-dire au lieu d’avoir un constructeur en appelant un autre, les deux appellent un ami privé!

2

Cette approche peut fonctionner pour certains types de classes (lorsque l'opérateur d'affectation se comporte bien):

Foo::Foo()
{
    // do what every Foo is needing
    ...
}

Foo::Foo(char x)
{
    *this = Foo();

    // do the special things for a Foo with char
    ...
}
2
V15I0N

Si je comprends bien votre question, vous demandez si vous pouvez appeler plusieurs constructeurs en C++?

Si c'est ce que vous recherchez, alors non, ce n'est pas possible.

Vous pouvez certainement avoir plusieurs constructeurs, chacun avec des signatures d'argument uniques, puis appeler celui que vous voulez lorsque vous instanciez un nouvel objet.

Vous pouvez même avoir un constructeur avec des arguments par défaut à la fin.

Mais vous ne pouvez pas avoir plusieurs constructeurs, puis appelez chacun d'eux séparément.

1
warren

En appelant un constructeur, il alloue effectivement de la mémoire, de la pile ou du tas. Donc, appeler un constructeur dans un autre constructeur crée une copie locale. Nous modifions donc un autre objet, pas celui sur lequel nous nous concentrons.

0
XPD

Ce serait plus facile à tester qu'à décider :) Essayez ceci:

#include <iostream>

class A {
public:
    A( int a) : m_a(a) {
        std::cout << "A::Ctor" << std::endl;    
    }
    ~A() {
        std::cout << "A::dtor" << std::endl;    
    }
public:
    int m_a;
};

class B : public A {
public:
    B( int a, int b) : m_b(b), A(a) {}
public:
    int m_b;
};

int main() {
    B b(9, 6);
    std::cout << "Test constructor delegation a = " << b.m_a << "; b = " << b.m_b << std::endl;    
    return 0;
}

et compilez avec 98 std: g ++ main.cpp -std = c ++ 98 -o test_1

tu verras:

A::Ctor
Test constructor delegation a = 9; b = 6
A::dtor

alors :)

0
gyula