web-dev-qa-db-fra.com

C++ - construction d'un objet dans une classe

Je suis assez nouveau en C++ et je ne suis pas sûr de celui-ci. Regardez l'exemple suivant qui résume mon problème actuel.

class Foo
{
    //stuff
};

class Bar
{
    Foo foo;
};

Donc, Bar constitue un objet Foo complet, pas seulement une référence ou un pointeur. Cet objet est-il initialisé par son constructeur par défaut? Dois-je appeler explicitement son constructeur, et si oui, comment et où?

Merci.

22
SolarBear

Il sera initialisé par son constructeur par défaut. Si vous voulez utiliser un constructeur différent, vous pourriez avoir quelque chose comme ceci:

class Foo
{
    public: 
    Foo(int val) { }
    //stuff
};

class Bar
{
    public:
    Bar() : foo(2) { }

    Foo foo;
};
21

La construction est un sujet assez difficile en C++. La réponse simple est cela dépend . Que Foo soit initialisé ou non dépend de la définition de Foo lui-même. À propos de la deuxième question: comment faire initialiser Bar Foo: les listes d’initialisation sont la réponse.

Bien que le consensus général soit que Foo sera initialisé par défaut par le constructeur implicite par défaut (compilateur généré), cela n’a pas besoin de rester à true. 

Si Foo n'a pas de constructeur par défaut défini par l'utilisateur, Foo ne sera pas initialisé. Pour être plus précis: chaque membre de Bar ou Foo manquant d’un constructeur par défaut défini par l’utilisateur sera non initialisé par le constructeur par défaut généré par le compilateur de Bar :

class Foo {
   int x;
public:
   void dump() { std::cout << x << std::endl; }
   void set() { x = 5; }
};
class Bar {
   Foo x;
public:
   void dump() { x.dump(); }
   void set() { x.set(); } 
};
class Bar2
{
   Foo x;
public:
   Bar2() : Foo() {}
   void dump() { x.dump(); }
   void set() { x.set(); }
};
template <typename T>
void test_internal() {
   T x;
   x.dump();
   x.set();
   x.dump();
}
template <typename T>
void test() {
   test_internal<T>();
   test_internal<T>();
}
int main()
{
   test<Foo>(); // prints ??, 5, 5, 5, where ?? is a random number, possibly 0
   test<Bar>(); // prints ??, 5, 5, 5
   test<Bar2>(); // prints 0, 5, 0, 5
}

Maintenant, si Foo avait un constructeur défini par l'utilisateur, il serait toujours initialisé, que Bar ait ou non un constructeur initialisé. Si Bar a un constructeur défini par l'utilisateur qui appelle explicitement le constructeur (éventuellement implicitement défini) de Foo, alors Foo sera en fait initialisé. Si la liste d'initialisation de Bar n'appelle pas le constructeur Foo, elle sera alors équivalente au cas où Bar n'aurait aucun constructeur défini par l'utilisateur.

Il peut être nécessaire d’expliquer le code de test. Nous cherchons à savoir si le compilateur initialise la variable sans que le code utilisateur appelle réellement le constructeur. Nous voulons tester si l'objet est initialisé ou non. Maintenant, si nous venons de créer un objet dans une fonction, il se peut qu’il atteigne une position de mémoire intacte et contenant déjà des zéros. Nous voulons différencier la chance du succès, nous définissons donc une variable dans une fonction et l'appelons deux fois. Lors de la première utilisation, le contenu de la mémoire est imprimé et une modification est forcée. Lors du deuxième appel à la fonction, la trace de la pile étant identique, la variable sera conservée exactement dans la même position de mémoire. Si elle était initialisée, elle serait mise à 0, sinon elle garderait la même valeur que l'ancienne variable avait exactement la même position.

Dans chacune des exécutions de test, la première valeur imprimée est la valeur initialisée (si elle a été réellement initialisée) ou la valeur dans cette position de mémoire, ce qui arrive dans certains cas à 0 à la position de mémoire après l'avoir changée manuellement. La troisième valeur provient de la deuxième exécution de la fonction. Si la variable est en cours d'initialisation, elle tombera à 0. Si l'objet n'est pas initialisé, sa mémoire conservera l'ancien contenu.

Le compilateur C++ génère quatre fonctions, si elle le peut et si vous ne les fournissez pas: un constructeur par défaut, un constructeur de copie, un opérateur d'affectation et un destructeur. Dans la norme C++ (chapitre 12, "Fonctions spéciales"), elles sont désignées "implicitement déclarées" et "implicitement définies". Ils auront un accès public.

Ne confondez pas "implicitement défini" avec "default" dans un constructeur. Le constructeur par défaut est celui qui peut être appelé sans aucun argument, s'il en existe un. Si vous ne fournissez aucun constructeur, un constructeur par défaut sera défini implicitement. Il utilisera les constructeurs par défaut pour chaque classe de base et membre de données.

Donc, ce qui se passe, c'est que la classe Foo a un constructeur par défaut défini implicitement, et Bar (qui ne semble pas avoir de constructeur défini par l'utilisateur) utilise son constructeur par défaut implicitement défini qui appelle le constructeur par défaut de Foo.

Si vous voulez écrire un constructeur pour Bar, vous pouvez mentionner foo dans sa liste d'initialisateurs, mais puisque vous utilisez le constructeur par défaut, vous n'avez pas besoin de le spécifier.

Rappelez-vous que si vous écrivez un constructeur pour Foo, le compilateur ne générera pas automatiquement de constructeur par défaut et vous devrez donc en spécifier un si vous en avez besoin. Par conséquent, si vous définissiez quelque chose comme Foo(int n); dans la définition de Foo et n'écriviez pas explicitement un constructeur par défaut (soit Foo();, soit Foo(int n = 0);), vous ne pourriez pas avoir de barre dans sa forme actuelle, car elle ne pourrait pas utiliser Le constructeur par défaut de Foo. Dans ce cas, vous devriez avoir un constructeur comme Bar(int n = 0): foo(n) {} avec le constructeur Bar initialiser le Foo. (Notez que Bar(int n = 0) {foo = n;} ou similaire ne fonctionnerait pas, car le constructeur Bar tenterait d'abord d'initialiser foo et cela échouerait.)

5
David Thornley

Si vous n'appelez pas explicitement un constructeur de foo à l'intérieur du constructeur de Bar, celui par défaut sera utilisé. Vous pouvez contrôler ceci en appelant explicitement le constructeur

Bar::Bar() : foo(42) {}

Ceci est bien sûr supposé que vous ajoutez un Foo :: Foo (int) au code :)

2
JaredPar

Constructeur destiné à définir un état initial d'objet. En cas de hiérarchie d'héritage, les objets de classe de base seront construits dans l'ordre de la hiérarchie d'héritage (relation IS-A dans la terminologie OO) suivis par les objets de classe dérivés.

De même, lorsqu'un objet est incorporé dans un autre objet (relation HAS-A dans OO termes ou confinement), les constructeurs de l'objet incorporé sont appelés dans l'ordre de leur déclaration.

Le compilateur analyse tous les membres de la classe B, puis génère le code pour chaque méthode. À ce stade, il connaît tous les membres et leur ordre, et il construira les membres dans l’ordre, en utilisant les constructeurs par défaut, sauf indication contraire. So foo est construit sans appeler explicitement son constructeur par défaut. Ce que vous devez faire, c'est simplement créer un objet de Bar.

1
user6882413

Donc, Bar constitue un objet Foo complet, pas seulement une référence ou un pointeur. Cet objet est-il initialisé par son constructeur par défaut?

Si Foo a un ctor par défaut, un objet de type Foo utilisera le ctor par défaut lorsque vous créez un objet de type Bar. Autrement, vous devez appeler le Foo ctor vous-même ou votre cie Bar fera déposer votre compilateur le 2 juillet.

Par exemple:

class Foo {
public:
 Foo(double x) {}
};

class Bar  {
 Foo x;
};

int main() {
 Bar b;
}

Ce qui précède aura le compilateur à se plaindre quelque chose comme:

"Dans le constructeur 'Bar :: Bar ()': Ligne 5: erreur: pas de fonction correspondante pour l'appel à 'Foo :: Foo ()'

Dois-je appeler explicitement son constructeur, et si oui, comment et où?

Modifiez l'exemple ci-dessus comme suit:

class Foo {
 public:
  Foo(double x) {} // non-trivial ctor
};

class Bar  {     
 Foo x;
public:
  Bar() : x(42.0) {} // non-default ctor, so public access specifier required
};

int main() {
 Bar b;
}
1
dirkgently

Objet complet. Non, il est construit par défaut dans le constructeur par défaut de Bar.

Maintenant, si Foo avait un constructeur qui ne prenait, par exemple, qu'un int. Vous auriez besoin d’un constructeur à Bar pour appeler le constructeur de Foo et dire en quoi cela consiste:

class Foo {
public:
    Foo(int x) { .... }
};

class Bar {
public:
    Bar() : foo(42) {}

    Foo foo;
};

Mais si Foo avait un constructeur par défaut Foo (), le compilateur générait automatiquement le constructeur de Bar, ce qui ferait appel à la valeur par défaut de Foo (c'est-à-dire Foo ()).

1
Macke

Sauf indication contraire de votre part, foo est initialisé à l'aide de son constructeur par défaut. Si vous souhaitez utiliser un autre constructeur, vous devez le faire dans la liste d'initialisation pour Bar:

Bar::Bar( int baz ) : foo( baz )
{
    // Rest of the code for Bar::Bar( int ) goes here...
}
1
Naaff

Vous n'avez pas besoin d'appeler explicitement le constructeur par défaut en C++, il sera appelé pour vous. Si vous voulez appeler un constructeur différent, vous pouvez faire ceci:

Foo foo(somearg)
0
Shane C. Mason