Consider the following example. When bar
is constructed, it gives it's base type (foo
) constructor the address of my_member.y
where my_member
is data member that hasn't been initialized yet.
struct foo {
foo(int * p_x) : x(p_x) {}
int * x;
};
struct member {
member(int p_y) : y(p_y) {}
int y;
};
struct bar : foo
{
bar() : foo(&my_member.y), my_member(42) {}
member my_member;
};
#include <iostream>
int main()
{
bar my_bar;
std::cout << *my_bar.x;
}
Is this well defined? Is it legal to take the address of an uninitialized object's data member? I've found this question about passing a reference to an uninitialized object but it's not quite the same thing. In this case, I'm using the member access operator .
on an uninitialized object.
It's true that the address of an object's data member shouldn't be changed by initialization, but that doesn't necessarily make taking that address well defined. Additionally, the ccpreference.com page on member access operators has this to say :
The first operand of both operators is evaluated even if it is not necessary (e.g. when the second operand names a static member).
I understand it to mean that in the case of &my_member.y
my_member
would be evaluated, which I believe is fine (like int x; x;
seems fine) but I can't find documentation to back that up either.
Unlike some programming languages, C/C++ does not initialize most variables to a given value (such as zero) automatically. Thus when a variable is given a memory address to use to store data, the default value of that variable is whatever (garbage) value happens to already be in that memory address!
Remarks. The local variable name has been used, that is, read from, before it has been assigned a value. In C and C++, local variables aren't initialized by default. Uninitialized variables can contain any value, and their use leads to undefined behavior.
Some people feel you should not use the this pointer in a constructor because the object is not fully formed yet. However you can use this in the constructor (in the { body } and even in the initialization list) if you are careful.
First let's make accurate the question.
What you are doing isn't using an uninitialized object, you are using an object not within its lifetime. my_member
is constructed after foo
, therefore the lifetime of my_member
hasn't begun in foo(&my_member.y)
.
From [basic.life]
before the lifetime of an object has started but after the storage which the object will occupy has been allocated [...], any glvalue that refers to the original object may be used but only in limited ways. [...] such a glvalue refers to allocated storage, and using the properties of the glvalue that do not depend on its value is well-defined. The program has undefined behavior if:
- the glvalue is used to access the object, or [...]
Here accessing it means specifically to either read or modify the value of the object.
The evaluation of my_member
yields a lvalue, and there is nothing necessitating a conversion to prvalue, hence it stays a lvalue. Likewise, the evaluation of my_member.y
is also a lvalue. We then conclude that no object's value have been accessed, this is well-defined.
Yes you are allowed to pass &my_member.y
to foo
's constructor, and even copy the pointer - which you do with x(p_x)
.
The behaviour on dereferencing that pointer though in foo
's constructor is undefined. (But you don't do that.)
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