I'd like to do this:
struct Derived;
struct Base{
Derived const& m_ref;
Base(Derived const& ref) : m_ref(ref){}
};
struct Derived: Base{
Derived(): Base(*this){}
};
But I seem to get unreliable behaviour (when used later on, m_ref
points to things that aren't valid Derived).
Is it permissible to construct a reference to Derived from *this
before the class has been initialised?
I appreciate that it is not valid to use such a reference until it has been initialised, but I don't see how changes to the initialisation of a class can affect references to it (since initialising it doesn't move it around in memory...).
I'm not sure what to call what I'm trying to do, so my search for information on this has drawn a blank...
Update:
I can't reproduce my problems with a simple test case, so it looks like it is probably okay (though I can't prove it, and would still welcome a definitive answer).
Suspect my problems arose from a broken copy-assignment operator. That's another matter altogether though!
Update 2 My copy constructor and copy-assignment operators were indeed to blame, and now this seems to work reliably. Still interested in whether or not it is well-defined behaviour though.
3.8/1 says:
The lifetime of an object of type T begins when: — storage with the proper alignment and size for type T is obtained, and — if T is a class type with a non-trivial constructor (12.1), the constructor call has completed.
3.8/5 says:
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. Such a pointer refers to allocated storage (3.7.3.2), and using the pointer as if the pointer were of type void*, is well-defined. Such a pointer may be dereferenced but the resulting lvalue may only be used in limited ways, as described below.
"Below" is 3.8/6:
Such an lvalue refers to allocated storage (3.7.3.2), and using the properties of the lvalue which do not depend on its value is well-defined.
...and then a list of things you can't do. Binding to a reference to the same, derived type is not among them.
I can't find anything elsewhere that might make your code invalid. Notably, despite the following phrase in 8.3.2/4:
A reference shall be initialized to refer to a valid object or function.
there doesn't seem to be any definition of "valid object" to speak of.
So, after much to-ing and fro-ing, I must conclude that it is legal.
Of course, that's not to say that it's in any way a good idea! It still looks like a bad design.
For example, if you later change your base constructor and any of the following become relevant (again from 3.8/6):
static_cast
(5.2.9) (except when the conversion is ultimately to char&
or unsigned char&
dynamic_cast
(5.2.7) or as the operand of typeid
....then your program will be undefined, and the compiler may emit no diagnostic for this!
Random other observations
I notice a couple of other interesting things whilst compiling this answer, and this is as good a place as any to share them.
First, 9.3.2 appears to leave the type of this
in a ctor-initializer accidentally unspecified. Bizarre!
Second, the criteria set on a pointer by 3.8/5 (not the same list that I quoted from 3.8/6) include:
If the object will be or was of a non-POD class type, the program has undefined behavior if [..] the pointer is implicitly converted (4.10) to a pointer to a base class type.
I believe that this renders the following innocuous-looking code undefined:
struct A {
A(A* ptr) {}
};
struct B : A {
B() : A(this) {}
};
int main() {
B b;
}
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