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
It probably refers to in-class member initializers. This allows you to initialize non-static data members at the point of declaration: struct Foo { explicit Foo(int i) : i(i) {} // x is initialized to 3.1416 int i = 42; double x = 3.1416; }; More on that in Bjarne Stroustrup's C++11 FAQ.
In C++11, the language has been extended to allow specifying an initializer in the declaration, but this is just a shorthand—the actual initialization still takes place at the top of the constructor. Initialization of data members will also still occur in order of declaration.
The initializer list is used to directly initialize data members of a class. An initializer list starts after the constructor name and its parameters.
To initialize a class member variable, put the initialization code in a static initialization block, as the following section shows. To initialize an instance member variable, put the initialization code in a constructor.
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.
The order of initialization of class members is as below.
From CPP standard (N4713), the relevant portion is highlighted:
15.6.2 Initializing bases and members [class.base.init] ...
13 In a non-delegating constructor, initialization proceeds in the following order:
(13.1) — First, and only for the constructor of the most derived class (6.6.2), virtual base classes are initialized in the order they appear on a depth-first left-to-right traversal of the directed acyclic graph of base classes, where “left-to-right” is the order of appearance of the base classes in the derived class base-specifier-list.
(13.2) — Then, direct base classes are initialized in declaration order as they appear in the base-specifier-list (regardless of the order of the mem-initializers).
(13.3) — Then, non-static data members are initialized in the order they were declared in the class definition (again regardless of the order of the mem-initializers).
(13.4) — Finally, the compound-statement of the constructor body is executed.
[ Note: The declaration order is mandated to ensure that base and member subobjects are destroyed in the reverse order of initialization. —end note ]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With