4

Coming from this question, I am going a step further:

C* c = static_cast<C*>(malloc(sizeof(C)));

As stated in the referenced question, accessing *c (its members) is undefined behaviour before a constructor is called. But, the pointer itself is valid, of course.

Now within the constructor, the members are available already and I should be able to take the address off.

Putting this together, I conclude that the following is legal:

class Y;
class X
{
    Y& y;
public:
    X(Y& y) : y(y) { } // non-trivial constructor!
};
class Y
{
    X& x;
public:
    Y(X& x) : x(x) { }
};
class Z
{
    X x;
    Y y;
public:
    Z() : x(y), y(x) { } 
};

as long as the constructor of X does not use the uninitialized reference to Y other than storing it somewhere.

Is this correct or have I overseen some important point (and thus producing UB again)? If I missed something, would it make a difference if I used pointers instead of references?

Community
  • 1
  • 1
Aconcagua
  • 24,880
  • 4
  • 34
  • 59

2 Answers2

2

It should be fine, but you are not allowed to use y inside of X(Y& y) as it was not yet initialized.

The relevant part that says it is not UB is:

3.7.5/6 n4140 (emphasis is mine)

Similarly, before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any glvalue that refers to the original object may be used but only in limited ways.

y is lvalue (which is also a glvalue), so above is relevant here. Accessing variables using such reference is UB.

Standard also says that reference to be valid it must (8.3.2/5):

... A reference shall be initialized to refer to a valid object or function.

But I have not found in standard what is valid object. Especially whether it means that its constructor has already been called. Using pointer instead of reference seems to not have this problem.

marcinj
  • 48,511
  • 9
  • 79
  • 100
  • See my question, 'as long as'... I provided the malloc-and-cast example for having an equivalence to something already known - in both cases, I have a valid pointer to memory that has not yet been initialized. Well, second case (my question): at least, if I concluded correctly, of course. The equivalence is not to Z, but to its member y (of type Y). – Aconcagua Jun 09 '16 at 11:59
  • @Aconcagua so you are asking if (as example) doing `y.c = 123;` inside of X constructor would cause UB? I suppose standard is clear here: this is what `limited ways` explains, among others: UB is if `(5.2) — the pointer is used to access a non-static data member or call a non-static member function of the object, or` – marcinj Jun 09 '16 at 12:08
  • But those rules applies as I understand when you use for example `new`, when `malloc` is used constructor will not be called anyway. – marcinj Jun 09 '16 at 12:12
  • Using c->someMember before the (placement) constructor is called is UB, same as if X(Y& y) used y.someMember. This point is clear and was *not* intended to ask either. Sorry if I expressed myself badly. But c (the pointer itself is valid), so I could do `C* cc = c;` without UB. Question then is, if this applies for the reference to an uninitialized member (Z::y) during construction of an object, too. – Aconcagua Jun 09 '16 at 12:20
  • @Aconcagua see my edit, its not more about references instead of pointers – marcinj Jun 09 '16 at 13:12
2

This is legitimate. To justify this, see the following from the standard, [basic.life]:

  1. Before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any pointer that refers to the storage location where the object will be or was located may be used but only in limited ways. For an object under construction or destruction, see 12.7. Otherwise, such a pointer refers to allocated storage (3.7.4.2), and using the pointer as if the pointer were of type void*, is well-defined. ...

...

  1. Similarly, before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any glvalue that refers to the original object may be used but only in limited ways. For an object under construction or destruction, see 12.7. Otherwise, such a glvalue refers to allocated storage (3.7.4.2), and using the properties of the glvalue that do not depend on its value is well-defined.

...

Merely taking a reference falls under the 'limited use' (for objects whose lifetime has not yet begun) criteria set out above.

Community
  • 1
  • 1
Smeeheey
  • 9,906
  • 23
  • 39