web-dev-qa-db-fra.com

Comment initialiser des variables de membre d'objet C++ dans le constructeur?

J'ai une classe qui a deux objets en tant que variables membres. Je ne veux pas que les constructeurs de ces membres soient appelés une fois déclarés, j'essaie donc de m'accrocher explicitement à un pointeur sur l'objet Je n'ai aucune idée de ce que je fais. o_O

Sur StackOverflow, il semble que je puisse trouver d'autres exemples de variables de membre d'objet, mais généralement le constructeur est appelé immédiatement, comme ceci:

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

Mais je veux que le constructeur MyClass appelle le constructeur AnotherClass. Voici à quoi ressemble mon code:

BigMommaClass.h

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

class BigMommaClass {

        public:
                BigMommaClass(int numba1, int numba2);

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

BigMommaClass.cpp

#include "BigMommaClass.h"

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

Voici l'erreur que je reçois 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 venir à cela d'une direction différente?

54
Logical Fallacy

Vous pouvez spécifier comment initialiser les membres dans la liste d'initialisation des membres:

BigMommaClass {
    BigMommaClass(int, int);

private:
    ThingOne thingOne;
    ThingTwo thingTwo;
};

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

Vous essayez de créer une ThingOne en utilisant operator= qui ne fonctionnera pas (syntaxe incorrecte). En outre, vous utilisez un nom de classe en tant que nom de variable, c'est-à-dire ThingOne* ThingOne. Tout d'abord, corrigeons les noms de variables:

private:
    ThingOne* t1;
    ThingTwo* t2;

Puisque ce sont des indicateurs, ils doivent indiquer quelque chose. Si l'objet n'a pas encore été construit, vous devrez le faire explicitement avec new dans votre constructeur BigMommaClass:

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

En règle générale, les listes d'initialisateurs sont préférées pour la construction, ce qui donne l'apparence suivante:

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

Cette question est un peu ancienne, mais en c ++ 11, voici une autre manière de "faire plus de travail" dans le constructeur avant d'initialiser vos variables de membre:

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 transmis au constructeur thingOnes. Vous pouvez bien sûr rendre le lambda aussi complexe que vous le souhaitez.

8
patmanpato

Je sais que c'est 5 ans plus tard, mais les réponses ci-dessus ne traitent pas de ce qui n'allait pas avec votre logiciel. (Eh bien, c'est Yuushi, mais je n'avais pas réalisé avant d'avoir tapé ceci - doh!). Ils répondent à la question dans le titre Comment initialiser les variables de membre d'objet C++ dans le constructeur? Ceci concerne les autres questions: Est-ce que j'utilise la bonne approche mais la syntaxe incorrecte? Ou devrais-je venir à cela d'une direction différente?

Le style de programmation est en grande partie une question d’opinion, mais une autre façon de faire autant que possible dans un constructeur est de limiter les constructeurs au strict minimum, en ayant souvent une fonction d’initialisation distincte. Il n’est pas nécessaire d’essayer de mettre toute l’initialisation dans un constructeur, cela n’empêche pas d’essayer de forcer des choses de temps en temps dans la liste d’initialisation des constructeurs.

Alors, 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, en fonction du contexte. 

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

Dans BigMommaClass, ThingOne est un pointeur.

Cela suppose que le compilateur puisse même donner un sens aux lignes et ne pas rester coincé dans une boucle en pensant que ThingOne est un pointeur sur quelque chose qui est lui-même un pointeur sur quelque chose qui est un pointeur sur ...

Plus tard, quand vous écrivez

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

n'oubliez pas 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;

Alors, 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 de double sens et le new manquant. (Vous pouvez laisser this-> si vous voulez!) Cela mis en place, je peux ajouter les lignes suivantes à un de mes programmes c ++ et le compiler correctement.

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 tu as écrit

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

l'utilisation de this-> indique au compilateur que le membre de gauche ThingOne est destiné à désigner le pointeur. Cependant, nous sommes dans BigMommaClass à ce moment-là et ce n’est pas nécessaire . Le problème réside dans le côté droit de l’égal à égal où ThingOne est censé désigner la classe. Donc, un autre moyen de rectifier vos problèmes aurait é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 changer l'interprétation de l'identifiant par le compilateur.

1
user3070485

Comme d’autres (comme d’autres l'ont déjà mentionné), je suis conscient du fait que cette question est ancienne, mais je tenais à signaler quelque chose à propos de la première réponse ( et excellente ) de@chriswho a proposé une solution à la situation dans laquelle les membres du groupe sont considérés comme des membres "true composites" (c'est-à-dire -PAScomme pointeurs NOR références ) . La note est un peu grande donc je vais la démontrer ici avec un exemple de code.

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

1) Pour chaque "objet composé" qui NE FAUT PAS a un ctor par défaut - vousDEVEZl'initialiser dans la liste d'initialisation deALLle ctor de la classe "père" (c'est-à-dire BigMommaClass ou MyClass dans les exemples d'origine et MyClass dans le code ci-dessous), s'il en existe plusieurs (voir InnerClass1 dans l'exemple ci-dessous). Cela signifie que vous pouvez mettre en commentaire les m_innerClass1(a) et m_innerClass1(15) UNIQUEMENT si vous activez le InnerClass1 default ctor. 

2) Pour chaque "objet composé" que DOIT avoir un ctor par défaut - vousPOUVEZl'initialiser dans la liste d'initialisation, mais cela fonctionnera aussi si vous choisissez de ne pas le faire (voir InnerClass2 dans l'exemple au dessous de).

Voir l'exemple de code (conforme sous Ubuntu 18.04 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 cotr
        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;
}
1
Guy Avraham