3

I'm having trouble with something that seems very easy, so I must be overlooking something.

I need to construct a class that has a field that is also a class (non-POD). The class of the field has a default constructor and a "real" constructor. The thing is that I really can't construct the field in the initializer list, because in reality the constructor has a parameter that is a vector which needs a somewhat complex for loop to fill.

Here is a minimal example that reproduces the problem.

ConstructorsTest.h:

class SomeProperty {
public:
    SomeProperty(int param1); //Ordinary constructor.
    SomeProperty();           //Default constructor.
    int param1;
};

class ConstructorsTest {
    ConstructorsTest();
    SomeProperty the_property;
};

ConstructorsTest.cpp:

#include "ConstructorsTest.h"

ConstructorsTest::ConstructorsTest() {
    the_property(4);
}

SomeProperty::SomeProperty(int param1) : param1(param1) {}
SomeProperty::SomeProperty() : param1(0) {} //Default constructor, doesn't matter.

But this gives a compile error:

ConstructorsTest.cpp: In constructor 'ConstructorsTest::ConstructorsTest()':
ConstructorsTest.cpp:4:19: error: no match for call to '(SomeProperty) (int)'
    the_property(4);
                  ^

It gives no suggestions like it usually would of what functions could have been intended instead.

In the above example I would just initialize the_property in the initializer list, but in reality the 4 is actually a complex vector that needs to be generated first, so I really can't. Moving the_property(4) to the initializer list causes the compilation to succeed.

Other similar threads mention that the object must have a default constructor, or that it can't be const. Both requirements seem to have been met, here.

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
Ghostkeeper
  • 2,830
  • 1
  • 15
  • 27

2 Answers2

3

You can't initialize data member inside the constructor's body. (the_property(4); is just trying to invoke the_property as a functor.) You can only assign them like:

ConstructorsTest::ConstructorsTest() {
    the_property = ...;
}

but in reality the 4 is actually a complex vector that needs to be generated first

You can add a member function which generate the necessary data, and use it to initialize the data member in member initializer list. e.g.

class ConstructorsTest {
    ...
    static int generateData();
};

int ConstructorsTest::generateData() {
    return ...;
}

ConstructorsTest::ConstructorsTest() : the_property(generateData()) {
}
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • Ah, so the trick is to actually assign them with `=`. I knew it was something simple! – Ghostkeeper Jul 14 '17 at 02:17
  • @Ghostkeeper Note that assignment approach will default-construct the data member and then assign it in the body of the constructor, it might be less efficient than using member intializer list, which will construct the data member directly (without assignment). – songyuanyao Jul 14 '17 at 02:22
1

You cannot initialize a variable twice.1 When your constructor has started, all member subobjects will have been constructed. If you do not provide a member initializer in the constructor, or a default member initializer in the class definition, then it will perform default initialization. Regardless of what form it takes, you can't construct it again.

Complex multi-statement initialization is best done via a lambda function:

ConstructorsTest::ConstructorsTest()
  : the_property( []{ /* Do Complex Initialization */}() )
{
}

1: Well... you can, but not like that. And you really shouldn't for cases as simple as this.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982