Is this C++ member initialization behavior well defined?

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 c'tor):


B


member


42


member


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:


A


B


B


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:


Aggregate


A


B


a


A


b


B


a


b


struct Aggregate

A a;
B b;

Aggregate() : a(b)
;



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


Aggregate


A


B


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?


a


b



edit: 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))


b


b


b


b


b


0


int



edit: this answer helped me understand that I need to distinguish between





If you switch their order in the class definition, it will have better defined behavior. Alternatively, don't use the reference to access the object (see here for a related question stackoverflow.com/questions/50020255/…).
– StoryTeller
Aug 27 at 8:28






I know how to "fix" it. What I am not sure about is, whether it should be fixed (as it's ugly) or must be fixed (because it's undefined behavior and therefore simply broken) ;)
– simon
Aug 27 at 8:35






What you display is broken. The example in the other question is not. It's a mine-field of standardese
– StoryTeller
Aug 27 at 8:37





You are using object before it has been initialized. Why do you think you shouldn't fix it?
– Daniel Langr
Aug 27 at 8:40





b is not valid. The reference to b is valid, but it is a reference to an object that was not constructed yet; it can be used in certain limited ways, but not in the way you have used it.
– n.m.
Aug 27 at 9:29



b


b




2 Answers
2



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.


B


b.printMember()


b



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 ]





By the time, the constructor of Aggregate is called, the constructors of a (first) and b (next) are already called — you seem to be confusing Aggregate and A, or something else. The statement doesn't appear to make sense.
– Ruslan
Aug 27 at 9:55



By the time, the constructor of Aggregate is called, the constructors of a (first) and b (next) are already called


Aggregate


A





@Ruslan: Edited, see if it appears proper now.
– P.W
Aug 27 at 10:11





No, I still don't get what you mean by that sentence. What exactly is not undefined behavior? The UB has already happened by the time that initialization is done — at the stage of calling the constructor of a.
– Ruslan
Aug 27 at 10:17


a





Yes, UB has happened at the construction of a. But after its construction, member has a certain value, whatever that value is. So using that value in construction of Aggregate is not UB. But the overall behaviour is UB.
– P.W
Aug 27 at 10:23



member





No it hasn't. After UB you can't tell the state of the program — the program may no longer exist, or appear to be in multiple states at the same time or whatever...
– Ruslan
Aug 27 at 10:25







By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

𛂒𛀶,𛀽𛀑𛂀𛃧𛂓𛀙𛃆𛃑𛃷𛂟𛁡𛀢𛀟𛁤𛂽𛁕𛁪𛂟𛂯,𛁞𛂧𛀴𛁄𛁠𛁼𛂿𛀤 𛂘,𛁺𛂾𛃭𛃭𛃵𛀺,𛂣𛃍𛂖𛃶 𛀸𛃀𛂖𛁶𛁏𛁚 𛂢𛂞 𛁰𛂆𛀔,𛁸𛀽𛁓𛃋𛂇𛃧𛀧𛃣𛂐𛃇,𛂂𛃻𛃲𛁬𛃞𛀧𛃃𛀅 𛂭𛁠𛁡𛃇𛀷𛃓𛁥,𛁙𛁘𛁞𛃸𛁸𛃣𛁜,𛂛,𛃿,𛁯𛂘𛂌𛃛𛁱𛃌𛂈𛂇 𛁊𛃲,𛀕𛃴𛀜 𛀶𛂆𛀶𛃟𛂉𛀣,𛂐𛁞𛁾 𛁷𛂑𛁳𛂯𛀬𛃅,𛃶𛁼

Edmonton

Crossroads (UK TV series)