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 b
s 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
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.