This is a spinoff from invoking the copy constructor within the constructor.
I believe that an object is fully formed and can be expected to behave as such by the end of the initialiser list (edit: I was wrong about this though!). Specifically, member functions and accessing local state from within the constructor itself will behave exactly as they would from any other member function.
This seems to be a slightly contentious point of view though, the alternative is that only once the constructor has returned normally is the object fully formed.
The following is a quick & dirty test case for this which shows all the member fields that are mentioned in the initialiser list being initialised and those that aren't getting default constructed.
#include <cstdio>
struct noise
{
noise() { printf("noise default constructed\n"); }
noise(int x) { printf("noise integer constructed %u\n", x); }
~noise() { printf("noise dtor\n"); }
};
struct invoke : public noise
{
noise init;
noise body;
invoke() : noise(3), init(4)
{
body = noise(5);
throw *this; // try to use the object before returning normally
}
~invoke() { printf("invoke dtor\n"); }
};
int main()
{
try
{
invoke i;
}
catch (...)
{
}
}
This prints, on my machine at least,
noise integer constructed 3
noise integer constructed 4
noise default constructed
noise integer constructed 5
noise dtor
noise dtor
noise dtor
noise dtor
invoke dtor
noise dtor
noise dtor
noise dtor
As always, it's difficult to distinguish works-as-specified from works-as-my-compiler-implemented! Is this actually UB?
Is an object fully constructed at the end of the initialiser list?
No it is not. The object this
is fully constructed at the end of the execution of the constructor.
However, all the members are constructed by the end of the initializer list.
The difference is subtle but it is important as it relates to the execution of the destructors. Every constructed member and base class is destructed if the this
object throws an exception during the execution of the constructor. The destructor of the this
object will only execute once it is fully constructed.
From the cppreference:
- For any object of class or aggregate types if it, or any of its subobjects, is initialized by anything other than the trivial default constructor, lifetime begins when initialization ends.
- For any object of class types whose destructor is not trivial, lifetime ends when the execution of the destructor begins.
Your example is well-defined behavior, but only just so.
To be clear, the "invoke dtor"
line we're seeing is from the destruction of your exception, not the top-level i
object.
Each member of the class is initialized by the time the constructor body starts, though the object itself is not initialized until the constructor body completes. This is why ~invoke
is not called on the top-level invoke object.
The throw *this
expression copy-initializes an invoke
object from *this
, which is allowed. (The standard explicitly states that "Member functions [...] can be called during construction or destruction".) Your copy-initialization is the default, which just copy-initializes all the members - which have all been initialized.
Then because your constructor body is exiting via exception, all the initialized members are destructed, the exception is propagated, caught, and then disposed of, calling ~invoke
, and in turn destroying those members as well.
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