106 votes

Comment initialiser les variables membres d'un objet C++ dans le constructeur ?

J'ai une classe qui a quelques objets comme variables membres. Je ne veux pas que les constructeurs de ces membres soient appelés lorsqu'ils sont déclarés. J'essaie donc de conserver un pointeur sur l'objet de manière explicite. Je n'ai aucune idée de ce que je suis en train de faire.

J'ai pensé que je pourrais peut-être faire ce qui suit, où le constructeur est appelé immédiatement lors de l'initialisation de la variable membre de l'objet :

class MyClass {
    public:
        MyClass(int n);
    private:
        AnotherClass another(100); // Construct AnotherClass right away!
};

Mais je veux le MyClass pour appeler le AnotherClass constructeur. Voici à quoi ressemble mon code :

FIle BigMommaClass.h

#include "ThingOne.h"
#include "ThingTwo.h"

class BigMommaClass {

        public:
                BigMommaClass(int numba1, int numba2);

        private:
                ThingOne* ThingOne;
                ThingTwo* ThingTwo;
};

FIle BigMommaClass.cpp

#include "BigMommaClass.h"

BigMommaClass::BigMommaClass(int numba1, int numba2) {
        this->ThingOne = ThingOne(100);
        this->ThingTwo = ThingTwo(numba1, numba2);
}

Voici l'erreur que j'obtiens lorsque j'essaie de compiler :

g++ -Wall -c -Iclasses -o objects/BigMommaClass.o classes/BigMommaClass.cpp
In file included from classes/BigMommaClass.cpp:1:0:
classes/BigMommaClass.h:12:8: error: declaration of âThingTwo* BigMommaClass::ThingTwoâ
classes/ThingTwo.h:1:11: error: changes meaning of âThingTwoâ from âclass ThingTwoâ
classes/BigMommaClass.cpp: In constructor âBigMommaClass::BigMommaClass(int, int)â:
classes/BigMommaClass.cpp:4:30: error: cannot convert âThingOneâ to âThingOne*â in assignment
classes/BigMommaClass.cpp:5:37: error: â((BigMommaClass*)this)->BigMommaClass::ThingTwoâ cannot be used as a function
make: *** [BigMommaClass.o] Error 1

Est-ce que j'utilise la bonne approche, mais la mauvaise syntaxe ? Ou devrais-je aborder la question sous un angle différent ?

114voto

chris Points 28950

Vous pouvez spécifier comment initialiser les membres dans la liste des initialisateurs de membres :

BigMommaClass {
    BigMommaClass(int, int);

private:
    ThingOne thingOne;
    ThingTwo thingTwo;
};

BigMommaClass::BigMommaClass(int numba1, int numba2)
    : thingOne(numba1 + numba2), thingTwo(numba1, numba2) {}

39voto

Yuushi Points 10656

Vous essayez de créer un ThingOne en utilisant operator= ce qui ne va pas fonctionner (syntaxe incorrecte). De plus, vous utilisez le nom d'une classe comme nom de variable, ThingOne* ThingOne . Tout d'abord, fixons les noms des variables :

private:
    ThingOne* t1;
    ThingTwo* t2;

Puisque ce sont des pointeurs, ils doivent pointer vers quelque chose. Si l'objet n'a pas encore été construit, vous devrez le faire de manière explicite avec new dans votre fichier BigMommaClass constructeur :

BigMommaClass::BigMommaClass(int n1, int n2)
{
    t1 = new ThingOne(100);
    t2 = new ThingTwo(n1, n2);
}

En général, les listes d'initialisation sont préférées pour la construction cependant, donc cela ressemblera à :

BigMommaClass::BigMommaClass(int n1, int n2)
    : t1(new ThingOne(100)), t2(new ThingTwo(n1, n2))
{ }

12voto

patmanpato Points 543

Cette question est un peu ancienne, mais voici une autre façon en C++11 de "faire plus de travail" dans le constructeur avant d'initialiser vos variables membres :

BigMommaClass::BigMommaClass(int numba1, int numba2)
    : thingOne([](int n1, int n2){return n1+n2;}(numba1,numba2)),
      thingTwo(numba1, numba2) {}

La fonction lambda ci-dessus sera invoquée et le résultat sera passé au constructeur de thingOnes. Vous pouvez bien sûr rendre la fonction lambda aussi complexe que vous le souhaitez.

7voto

user3070485 Points 11

Je sais que c'est 5 ans plus tard, mais les réponses ci-dessus ne répondent pas à ce qui n'allait pas avec votre logiciel. (Enfin, celle de Yuushi le fait, mais je ne l'ai pas réalisé avant d'avoir tapé ceci - doh !). Elles répondent à la question posée dans le titre Comment initialiser les variables membres d'un objet C++ dans le constructeur ? Il s'agit des autres questions : Est-ce que j'utilise la bonne approche mais la mauvaise syntaxe ? Ou devrais-je aborder la question sous un angle différent ?

Le style de programmation est en grande partie une question d'opinion, mais un point de vue alternatif à l'idée de faire autant que possible dans un constructeur est de garder les constructeurs au strict minimum, en ayant souvent une fonction d'initialisation séparée. Il n'est pas nécessaire d'essayer de fourrer toute l'initialisation dans un constructeur, sans parler d'essayer de forcer les choses à certains moments dans la liste d'initialisation des constructeurs.

Donc, pour en venir au fait, qu'est-ce qui n'allait pas avec votre logiciel ?

private:
    ThingOne* ThingOne;
    ThingTwo* ThingTwo;

Notez qu'après ces lignes, ThingOne (et ThingTwo ) ont maintenant deux significations, selon le contexte.

En dehors de la classe de BigMomma, ThingOne est la classe que vous avez créée avec #include "ThingOne.h"

Dans la BigMommaClass, ThingOne est un pointeur.

En supposant que le compilateur puisse même donner un sens aux lignes et ne reste pas bloqué dans une boucle en pensant que ThingOne est un pointeur vers quelque chose qui est lui-même un pointeur vers quelque chose qui est un pointeur vers ...

Plus tard, quand vous écrirez

this->ThingOne = ThingOne(100);
this->ThingTwo = ThingTwo(numba1, numba2);

Gardez à l'esprit qu'à l'intérieur de BigMommaClass votre ThingOne est un pointeur.

Si vous modifiez les déclarations des pointeurs pour inclure un préfixe (p)

private:
    ThingOne* pThingOne;
    ThingTwo* pThingTwo;

Puis ThingOne fera toujours référence à la classe et pThingOne au pointeur.

Il est alors possible de réécrire

this->ThingOne = ThingOne(100);
this->ThingTwo = ThingTwo(numba1, numba2);

comme

pThingOne = new ThingOne(100);
pThingTwo = new ThingTwo(numba1, numba2);

qui corrige deux problèmes : le problème du double sens, et le manquant. new . (Vous pouvez laisser this-> si vous voulez !)

Avec cela en place, je peux ajouter les lignes suivantes à un de mes programmes C++ et il compile bien.

class ThingOne{public:ThingOne(int n){};};
class ThingTwo{public:ThingTwo(int x, int y){};};

class BigMommaClass {

    public:
            BigMommaClass(int numba1, int numba2);

    private:
            ThingOne* pThingOne;
            ThingTwo* pThingTwo;
};

BigMommaClass::BigMommaClass(int numba1, int numba2)
{
    pThingOne = new ThingOne(numba1 + numba2);
    pThingTwo = new ThingTwo(numba1, numba2);
};

Quand vous avez écrit

this->ThingOne = ThingOne(100);
this->ThingTwo = ThingTwo(numba1, numba2);

l'utilisation de this-> indique au compilateur que le côté gauche ThingOne est censé signifier le pointeur. Cependant, nous sommes à l'intérieur BigMommaClass à ce moment-là et ce n'est pas nécessaire.

Le problème se situe au niveau du côté droit des égalités où ThingOne s'entend de la classe. Une autre façon de rectifier vos problèmes aurait donc été d'écrire

this->ThingOne = new ::ThingOne(100);
this->ThingTwo = new ::ThingTwo(numba1, numba2);

ou simplement

ThingOne = new ::ThingOne(100);
ThingTwo = new ::ThingTwo(numba1, numba2);

en utilisant :: pour modifier l'interprétation de l'identifiant par le compilateur.

3voto

Guy Avraham Points 1264

Concernant le premier ( et grand ) réponse de chris qui a proposé une solution à la situation où les membres de la classe sont tenus comme un " véritable composite Les membres " (c'est-à-dire - pas como pointeurs ni références ) :

La note est un peu volumineuse, je vais donc la démontrer ici avec un exemple de code.

Lorsque vous choisissez de retenir les membres comme je l'ai mentionné, vous devez également garder à l'esprit ces deux choses :

  1. Pour chaque "objet composé" qui n'est pas ont un constructeur par défaut - vous debe l'initialiser dans la liste d'initialisation de tous les constructeurs de la classe "père" (i.e. - BigMommaClass o MyClass dans les exemples originaux et MyClass dans le code ci-dessous), dans le cas où il y en a plusieurs (cf. InnerClass1 dans l'exemple ci-dessous). En d'autres termes, vous pouvez "commenter" l'élément m_innerClass1(a) y m_innerClass1(15) uniquement si vous activez l'option InnerClass1 Constructeur par défaut.

  2. Pour chaque "objet composé" qui a un constructeur par défaut - vous puede l'initialiser dans la liste d'initialisation, mais cela fonctionnera également si vous avez choisi de ne pas le faire (cf. InnerClass2 dans l'exemple ci-dessous).

Voir l'exemple de code (compilé sous Ubuntu 18.04 (Castor bionique) avec g++ version 7.3.0) :

#include <iostream>

using namespace std;

class InnerClass1
{
    public:
        InnerClass1(int a) : m_a(a)
        {
            cout << "InnerClass1::InnerClass1 - set m_a:" << m_a << endl;
        }

        /* No default constructor
        InnerClass1() : m_a(15)
        {
            cout << "InnerClass1::InnerClass1() - set m_a:" << m_a << endl;
        }
        */

        ~InnerClass1()
        {
            cout << "InnerClass1::~InnerClass1" << endl;
        }

    private:
        int m_a;
};

class InnerClass2
{
    public:
        InnerClass2(int a) : m_a(a)
        {
            cout << "InnerClass2::InnerClass2 - set m_a:" << m_a << endl;
        }

        InnerClass2() : m_a(15)
        {
            cout << "InnerClass2::InnerClass2() - set m_a:" << m_a << endl;
        }

        ~InnerClass2()
        {
            cout << "InnerClass2::~InnerClass2" << endl;
        }

    private:
        int m_a;
};

class MyClass
{
    public:
        MyClass(int a, int b) : m_innerClass1(a), /* m_innerClass2(a),*/ m_b(b)
        {
            cout << "MyClass::MyClass(int b) - set m_b to:" << m_b << endl;
        }

         MyClass() : m_innerClass1(15), /*m_innerClass2(15),*/ m_b(17)
        {
            cout << "MyClass::MyClass() - m_b:" << m_b << endl;
        }

        ~MyClass()
        {
            cout << "MyClass::~MyClass" << endl;
        }

    private:
        InnerClass1 m_innerClass1;
        InnerClass2 m_innerClass2;
        int m_b;
};

int main(int argc, char** argv)
{
    cout << "main - start" << endl;

    MyClass obj;

    cout << "main - end" << endl;
    return 0;
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X