Search code examples
c++constructorinitializationundefined-behaviordefault-constructor

Is this C++ member initialization behavior well defined?


Let's assume we have a class B that has a member which is default initialized to 42. This class knows how to print the value of its member (it does so in the constructor):

struct B
{
  B() : member(42) { printMember(); }

  void printMember() const { std::cout << "value: " << member << std::endl; }

  int member;
};

Then we add a class A which receives a const reference to a B and asks B to print its value:

struct A
{
  A(const B& b) { b.printMember(); }
};

Finally we add another class Aggregate that aggregates an A and a B. The tricky part is that object a of type A is declared before object b type B, but then a is initialized using a (not yet valid?) reference to b:

struct Aggregate
{
  A a;
  B b;

  Aggregate() : a(b) { }
};

Consider the output of creating an Aggregate (I have added some logging to both the constructor and destructor of A and B) (Try it online!):

a c'tor
value: 0
b c'tor
value: 42
b d'tor
a d'tor

Am I right to assume that it is invalid to initialize a with a reference to a (not yet valid) instance of b and that this is therefore undefined behavior?


I am aware of the initialization order. This is what makes me struggle. I know that b is not yet constructed, but I also think to know that b's future address can be determined even before b is constructed. Therefore I assumed there could be some rule that I am not aware of that allows the compiler to default initialize bs members prior to b's construction or something like that. (It would have been more obvious if the first printed out value would have been something that looks random rather than 0 (the default value of int)).


This answer helped me understand that I need to distinguish between

  • binding a reference to an uninitialized object (which is valid) and
  • accessing by reference an uninitialized object (which is undefined)

Solution

  • Yes, you are right that it is UB, but for different reasons than just storing a reference to an object that hasn't been constructed.

    Construction of class members happens in order of their appearance in the class. Although the address of B is not going to change and technically you can store a reference to it, as @StoryTeller pointed out, calling b.printMember() in the constructor with b that hasn't been constructed yet is definitely UB.