How can I initialize C++ class data members in the constructor?
Asked Answered
T

5

119

I've got a class that has a couple of data members of class type. I don't want the constructors for these members to be called when declared, so I'm trying to hang onto a pointer to the object explicitly.

I thought maybe I could do the following, where the constructor is called immediately when initializing the data members:

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

But I want the MyClass constructor to call the AnotherClass constructor. Here's what my code looks like:

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

Here's the error I'm getting when I try to compile:

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

Am I using the right approach, but the wrong syntax? Or should I be coming at this from a different direction?

Thump answered 17/10, 2012 at 4:33 Comment(4)
Do you just want to call it from there so that you can use the arguments?Obliteration
@chris, right now, I think that's all I have going on. But I'd also like to know how to do this if, say I needed to do something before passing the arguments: Like add "numba1" and "numba2" and pass the sum to a member variable constructor.Thump
Well, your immediate error is that you're assigning an object to a pointer (you'd need a new, but there are better alternatives anyway). The issue at hand can be solved with the member initializers, however.Obliteration
private: AnotherClass another(100); // this constructs AnotherClass right away! Does it? My compiler really does not accept this, unless the declaration is inside a function. This line is interpreted as a function declaration, and the compiler expects an argument list, not a constant or variable.Large
O
129

You can specify how to initialize members in the member initializer list:

BigMommaClass {
    BigMommaClass(int, int);

private:
    ThingOne thingOne;
    ThingTwo thingTwo;
};

BigMommaClass::BigMommaClass(int numba1, int numba2)
    : thingOne(numba1 + numba2), thingTwo(numba1, numba2) {}
Obliteration answered 17/10, 2012 at 4:39 Comment(3)
It's still bothering me that with this syntax, I can't do any real work in the constructor to pass onto the other constructors. Constructors should probably be lightweight, so I'm having trouble coming up with a real-world example, but I find myself wishing the syntax looked more like this.thingOne = new ThingOne(100); because that just offers more flexibility. But I digress.Thump
Does thingOne and thingTwo have to be initialised in BigMommaClass's constructor? Or can it be done in some other function which is called after?Protuberate
@nobism, For initializaion rather than assignment, it has to be done in a constructor. You are allowed to do something like : thingOne(calculateThing1(numba1, numba2)). In rare cases, you might need initialization, but have no way to do it in the initializer list. In that case, it's possible to use something like std::optional or ts::deferred_construction. However, a lot of thinking would have to go into that decision.Obliteration
S
42

You're trying to create a ThingOne by using operator= which isn't going to work (incorrect syntax). Also, you're using a class name as a variable name, that is, ThingOne* ThingOne. Firstly, let's fix the variable names:

private:
    ThingOne* t1;
    ThingTwo* t2;

Since these are pointers, they must point to something. If the object hasn't been constructed yet, you'll need to do so explicitly with new in your BigMommaClass constructor:

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

Generally initializer lists are preferred for construction however, so it will look like:

BigMommaClass::BigMommaClass(int n1, int n2)
    : t1(new ThingOne(100)), t2(new ThingTwo(n1, n2))
{ }
Subtreasury answered 17/10, 2012 at 4:39 Comment(10)
thanks. So should I have ThingOne* t1 or should I just use ThingOne t1?Thump
@DavidEnglund, Use the latter. Unnecessary dynamic allocation is bad. If you really needed it, a smart pointer would have been the way to go.Obliteration
@chris, thanks. I was using the pointers in an effort to prevent the constructors from being called right away. I think that's cleared up now, though.Thump
@DavidEnglund, I just want to point out that specifying arguments at the declaration in the class is C++11 only anyway, but even it doesn't call it right away. When that object is initialized, that will just be the default if you don't override it. Look up in-class member initialization for more info on that. I think you also have to use uniform initialization to do that as well if it isn't just an implicit constructor with one parameter that you can get with ThingOne thingOne = 100;.Obliteration
@DavidEnglund As others have said, dynamic allocation is unnecessary in this case. There are use cases for it however (pimpl idiom, polymorphic classes, only requiring forward declarations to help reduce compile times etc).Subtreasury
@Yuushi, like for lazy loading?Thump
@DavidEnglund That's a separate topic again, but yes, you could use it for that in principle.Subtreasury
Do we need to add a destructor of the BigMommaClass in which the objects t1 and t2 are deleted?Tuner
@Tuner Yes, it should, although it should be using unique_ptr and make_unique instead of raw new to obviate the need for that - I just didn't want to throw too many concepts in at once.Subtreasury
Thank you, @Subtreasury . I have finally come up with a solution based on your answer in which I have, however, used shared_ptr and make_shared.Tuner
F
13

This question is a bit old, but here's another way in C++11 of "doing more work" in the constructor before initialising your member variables:

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

The lambda function above will be invoked and the result passed to thingOnes constructor. You can of course make the lambda as complex as you like.

Foodstuff answered 3/12, 2015 at 23:28 Comment(1)
the parentheses don't balance.Saguache
W
7

I know this is 5 years later, but the replies above don't address what was wrong with your software. (Well, Yuushi's does, but I didn't realise until I had typed this - doh!). They answer the question in the title How can I initialize C++ object member variables in the constructor? This is about the other questions: Am I using the right approach but the wrong syntax? Or should I be coming at this from a different direction?

Programming style is largely a matter of opinion, but an alternative view to doing as much as possible in a constructor is to keep constructors down to a bare minimum, often having a separate initialization function. There is no need to try to cram all initialization into a constructor, never mind trying to force things at times into the constructors initialization list.

So, to the point, what was wrong with your software?

private:
    ThingOne* ThingOne;
    ThingTwo* ThingTwo;

Note that after these lines, ThingOne (and ThingTwo) now have two meanings, depending on context.

Outside of BigMommaClass, ThingOne is the class you created with #include "ThingOne.h"

Inside BigMommaClass, ThingOne is a pointer.

That is assuming the compiler can even make sense of the lines and doesn't get stuck in a loop thinking that ThingOne is a pointer to something which is itself a pointer to something which is a pointer to ...

Later, when you write

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

bear in mind that inside of BigMommaClass your ThingOne is a pointer.

If you change the declarations of the pointers to include a prefix (p)

private:
    ThingOne* pThingOne;
    ThingTwo* pThingTwo;

Then ThingOne will always refer to the class and pThingOne to the pointer.

It is then possible to rewrite

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

as

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

which corrects two problems: the double meaning problem, and the missing new. (You can leave this-> if you like!)

With that in place, I can add the following lines to a C++ program of mine and it compiles nicely.

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

When you wrote

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

the use of this-> tells the compiler that the left hand side ThingOne is intended to mean the pointer. However we are inside BigMommaClass at the time and it's not necessary.

The problem is with the right hand side of the equals where ThingOne is intended to mean the class. So another way to rectify your problems would have been to write

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

or simply

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

using :: to change the compiler's interpretation of the identifier.

Wader answered 30/8, 2018 at 12:0 Comment(0)
P
4

Regarding the first (and great) answer from chris who proposed a solution to the situation where the class members are held as a "true composite" members (i.e.- not as pointers nor references):

The note is a bit large, so I will demonstrate it here with some sample code.

When you chose to hold the members as I mentioned, you have to keep in mind also these two things:

  1. For every "composed object" that does not have a default constructor - you must initialize it in the initialization list of all the constructor's of the "father" class (i.e.- BigMommaClass or MyClass in the original examples and MyClass in the code below), in case there are several (see InnerClass1 in the example below). Meaning, you can "comment out" the m_innerClass1(a) and m_innerClass1(15) only if you enable the InnerClass1 default constructor.

  2. For every "composed object" that does have a default constructor - you may initialize it within the initialization list, but it will work also if you chose not to (see InnerClass2 in the example below).

See sample code (compiled under Ubuntu 18.04 (Bionic Beaver) with g++ version 7.3.0):

#include <iostream>

class InnerClass1
{
    public:
        InnerClass1(int a) : m_a(a)
        {
            std::cout << "InnerClass1::InnerClass1 - set m_a:" << m_a << '\n';
        }

        /* No default constructor
        InnerClass1() : m_a(15)
        {
            std::cout << "InnerClass1::InnerClass1() - set m_a:" << m_a << '\n';
        }
        */

        ~InnerClass1()
        {
            std::cout << "InnerClass1::~InnerClass1" << '\n';
        }

    private:
        int m_a;
};

class InnerClass2
{
    public:
        InnerClass2(int a) : m_a(a)
        {
            std::cout << "InnerClass2::InnerClass2 - set m_a:" << m_a << '\n';
        }

        InnerClass2() : m_a(15)
        {
            std::cout << "InnerClass2::InnerClass2() - set m_a:" << m_a << '\n';
        }

        ~InnerClass2()
        {
            std::cout << "InnerClass2::~InnerClass2" << '\n';
        }

    private:
        int m_a;
};

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

         MyClass() : m_innerClass1(15), /*m_innerClass2(15),*/ m_b(17)
        {
            std::cout << "MyClass::MyClass() - m_b:" << m_b << '\n';
        }

        ~MyClass()
        {
            std::cout << "MyClass::~MyClass" << '\n';
        }

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

int main(int argc, char** argv)
{
    std::cout << "main - start" << '\n';

    MyClass obj;

    std::cout << "main - end" << '\n';
    return 0;
}
Preter answered 20/12, 2018 at 16:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.