Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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)
like image 973
simon Avatar asked Aug 27 '18 08:08

simon


People also ask

What is member initialization?

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.

Can we initialize data members in a class C?

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.

What is initialization list in C?

The initializer list is used to directly initialize data members of a class. An initializer list starts after the constructor name and its parameters.

How do you initialize a class member?

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.


2 Answers

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.

like image 198
Dev Null Avatar answered Oct 21 '22 14:10

Dev Null


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 ]

like image 22
P.W Avatar answered Oct 21 '22 13:10

P.W