Hoe kan ik C++-objectlidvariabelen initialiseren in de constructor?

Ik heb een klasse die een aantal objecten als lidvariabelen heeft. Ik wil niet dat de constructors voor deze leden worden aangeroepen wanneer ze worden gedeclareerd, dus ik probeer expliciet een verwijzing naar het object vast te houden. Ik heb geen idee wat ik aan het doen ben.

Ik dacht dat ik misschien het volgende kon doen, waarbij de constructor onmiddellijk wordt aangeroepen bij het initialiseren van de objectlidvariabele:

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

Maar ik wil dat de MyClass-constructor de AnotherClass-constructor aanroept. Zo ziet mijn code eruit:

Bestand BigMommaClass.h

#include "ThingOne.h"
#include "ThingTwo.h"
class BigMommaClass {
        public:
                BigMommaClass(int numba1, int numba2);
        private:
                ThingOne* ThingOne;
                ThingTwo* ThingTwo;
};

Bestand BigMommaClass.cpp

#include "BigMommaClass.h"
BigMommaClass::BigMommaClass(int numba1, int numba2) {
        this->ThingOne = ThingOne(100);
        this->ThingTwo = ThingTwo(numba1, numba2);
}

Dit is de foutmelding die ik krijg als ik probeer te compileren:

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

Gebruik ik de juiste aanpak, maar de verkeerde syntaxis? Of moet ik dit vanuit een andere richting benaderen?


Antwoord 1, autoriteit 100%

U kunt specificeren hoe leden geïnitialiseerd moeten worden in de ledeninitialisatielijst:

BigMommaClass {
    BigMommaClass(int, int);
private:
    ThingOne thingOne;
    ThingTwo thingTwo;
};
BigMommaClass::BigMommaClass(int numba1, int numba2)
    : thingOne(numba1 + numba2), thingTwo(numba1, numba2) {}

Antwoord 2, autoriteit 34%

Je probeert een ThingOnete maken met behulp van operator=wat niet gaat werken (onjuiste syntaxis). U gebruikt ook een klassenaam als variabelenaam, dat wil zeggen ThingOne* ThingOne. Laten we eerst de namen van variabelen corrigeren:

private:
    ThingOne* t1;
    ThingTwo* t2;

Aangezien dit wijzers zijn, moeten ze ergens naar verwijzen. Als het object nog niet is geconstrueerd, moet u dit expliciet doen met new in uw BigMommaClass-constructor:

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

Over het algemeen hebben initialisatielijsten echter de voorkeur voor constructie, dus het ziet er als volgt uit:

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

Antwoord 3, autoriteit 11%

Deze vraag is een beetje oud, maar hier is een andere manier in C++11 om “meer werk te doen” in de constructor voordat je je lidvariabelen initialiseert:

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

De bovenstaande lambda-functie wordt aangeroepen en het resultaat wordt doorgegeven aan de constructor thingOnes. Je kunt de lambda natuurlijk zo complex maken als je zelf wilt.


Antwoord 4, autoriteit 6%

Ik weet dat dit 5 jaar later is, maar de bovenstaande antwoorden gaan niet in op wat er mis was met uw software. (Nou, die van Yuushi wel, maar ik realiseerde het me pas toen ik dit typte – doh!). Ze beantwoorden de vraag in de titel Hoe kan ik C++-objectlidvariabelen initialiseren in de constructor?Dit gaat over de andere vragen: Gebruik ik de juiste benadering maar de verkeerde syntaxis? Of moet ik dit vanuit een andere richting benaderen?

De programmeerstijl is grotendeels een kwestie van mening, maar een alternatief voor zoveel mogelijk doen in een constructor is om constructeurs tot een absoluut minimum te beperken, vaak met een aparte initialisatiefunctie. Het is niet nodig om te proberen alle initialisatie in een constructor te proppen, laat staan ​​te proberen dingen soms in de initialisatielijst van de constructor te forceren.

Dus, tot op het punt, wat was er mis met je software?

private:
    ThingOne* ThingOne;
    ThingTwo* ThingTwo;

Merk op dat na deze regels ThingOne(en ThingTwo) nu twee betekenissen hebben, afhankelijk van de context.

Buiten BigMommaClass is ThingOnede les die je hebt gemaakt met #include "ThingOne.h"

Binnen BigMommaClass is ThingOneeen aanwijzer.

Dat veronderstelt dat de compiler zelfs de regels kan begrijpen en niet vast komt te zitten in een lus door te denken datThingOneeen verwijzing is naar iets dat is zelf een verwijzing naar iets dat een verwijzing is naar …

Later, als je schrijft

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

houd er rekening mee dat in BigMommaClassje ThingOneeen aanwijzer is.

Als u de declaraties van de aanwijzers wijzigt om een ​​voorvoegsel (p) op te nemen

private:
    ThingOne* pThingOne;
    ThingTwo* pThingTwo;

Dan zal ThingOnealtijd naar de klasse verwijzen en pThingOnenaar de aanwijzer.

Het is dan mogelijk om te herschrijven

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

als

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

die twee problemen corrigeert: het probleem met de dubbele betekenis en het ontbrekende new. (Je kunt this->achterlaten als je wilt!)

Als dat op zijn plaats is, kan ik de volgende regels toevoegen aan een C++-programma van mij en het compileert goed.

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);
};

Toen je schreef

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

het gebruik van this->vertelt de compiler dat de linkerkant ThingOnebedoeld is om de aanwijzer te betekenen. We zijn op dat moment echter in BigMommaClassen dat is niet nodig.

Het probleem is met de rechterkant van de gelijken, waar ThingOnede klasse bedoelt. Dus een andere manier om je problemen op te lossen zou zijn geweest om te schrijven

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

of gewoon

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

gebruik ::om de interpretatie van de identifier door de compiler te wijzigen.


Antwoord 5, autoriteit 3%

Met betrekking tot de eerste (en geweldig) antwoord van chrisdie een oplossing voorstelde voor de situatie waarin de klasleden worden vastgehouden als een “>true composiet” leden (dwz- nietals pointersnochreferenties):

De notitie is een beetje groot, dus ik zal het hier demonstreren met wat voorbeeldcode.

Als je ervoor kiest om de leden vast te houden, zoals ik al zei, moet je ook deze twee dingen in gedachten houden:

  1. Voor elk “samengesteld object” dat geeneen standaardconstructor heeft – moethet initialiseren in de initialisatielijst van allede constructeurs van de “vader”-klasse (dwz BigMommaClassof MyClassin de originele voorbeelden en MyClassin de onderstaande code), voor het geval er zijn er meerdere (zie InnerClass1in het onderstaande voorbeeld). Dit betekent dat u de m_innerClass1(a)en m_innerClass1(15)alleenkunt “uitspreken” als u de InnerClass1standaard constructor.

  2. Voor elk “samengesteld object” dat weleen standaardconstructor heeft – u maghet initialiseren binnen de initialisatielijst, maar het zal ook werken als u ervoor kiest niet (zie InnerClass2in het onderstaande voorbeeld).

Zie voorbeeldcode (samengesteld onder Ubuntu 18.04(Bionic Beaver) met g++versie 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;
}

Other episodes