Suppose I have a class whose constructor spawns a thread that deletes the object:
class foo {
public:
foo()
: // initialize other data-members
, t(std::bind(&foo::self_destruct, this))
{}
private:
// other data-members
std::thread t;
// no more data-members declared after this
void self_destruct() {
// do some work, possibly involving other data-members
delete this;
}
};
The problem here is that the destructor might get invoked before the constructor has finished. Is this legal in this case? Since t
is declared (and thus initialized) last, and there is no code in the constructor body, and I never intend to subclass this class, I assume that the object has been completely initialized when self_destruct
is called. Is this assumption correct?
I know that the statement delete this;
is legal in member-functions if this
is not used after that statement. But constructors are special in several ways, so I am not sure if this works.
Also, if it is illegal, I am not sure how to work around it, other spawning the thread in a special initialization-function that must be called after construction of the object, which I really would like to avoid.
P.S.: I am looking for an answer for C++03 (I am restricted to an older compiler for this project). The std::thread
in the example is just for illustration-purposes.
Firstly, we see that an object of type foo
has non-trivial initialization because its constructor is non-trivial (§3.8/1):
An object is said to have non-trivial initialization if it is of a class or aggregate type and it or one of its members is initialized by a constructor other than a trivial default constructor.
Now we see that an object of type foo
's lifetime begins after the constructor ends (§3.8/1):
The lifetime of an object of type
T
begins when:
- storage with the proper alignment and size for type T is obtained, and
- if the object has non-trivial initialization, its initialization is complete.
Now, it is undefined behaviour if you do delete
on the object before the end of the constructor if the type foo
has a non-trivial destructor (§3.8/5):
Before the lifetime of an object has started but after the storage which the object will occupy has been allocated [...] 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, [...]
So since our object is under construction, we take a look at §12.7:
Member functions, including virtual functions (10.3), can be called during construction or destruction (12.6.2).
That means that it's fine for self_destruct
to be called while the object is being constructed. However, this section says nothing specifically about destroying an object while it is being constructed. So I suggest we look at the operation of the delete-expression
.
First, it "will invoke the destructor (if any) for the object [...] being deleted." The destructor is a special case of member function, so it is fine to call it. However, §12.4 Destructors says nothing about whether it is well-defined when the destructor is called during construction. No luck here.
Second, "the delete-expression will call a deallocation function" and "the deallocation function shall deallocate the storage referenced by the pointer". Once again, nothing is said about doing this to storage that is currently being used be an object under construction.
So I argue that this is undefined behaviour by the fact that the standard hasn't defined it very precisely.
Just to note: the lifetime of an object of type foo
ends when the destructor call starts, because it has a non-trivial destructor. So if delete this;
occurs before the end of the object's construction, its lifetime ends before it starts. This is playing with fire.
I daresay it is well-defined to be illegal (though it might obviously still work with some compilers).
This is somewhat the same situation as "destructor not called when exception is thrown from constructor".
A delete-expression, according to the standard, destroys a most derived object (1.8) or array created by a new-expression (5.3.2). Before the end of the constructor, an object is not a most derived object, but an object of its direct ancestor's type.
Your class foo
has no base class, so there is no ancestor, this
therefore has no type and your object is not really an object at all at the time delete
is called. But even if there was a base class, the object would be a not-most-derived object (still rendering it illegal), and the wrong constructor would be called.
Formally the object doesn't exist until the constructor has finished successfully. Part of the reason is that the constructor might be called from a derived class' constructor. In that case you certainly don't want to destroy the constructed sub-object via an explicit destructor call, and even less invoke UB by calling delete this
on a (part of a) not completely constructed object.
Standardese about the object existence, emphasis added:
C++11 §3.8/1:
The lifetime of an object is a runtime property of the object. An object is said to have non-trivial initialization if it is of a class or aggregate type and it or one of its members is initialized by a constructor other than a trivial default constructor. [Note: initialization by a trivial copy/move constructor is non-trivial initialization. —end note ] The lifetime of an object of type T begins when:
— storage with the proper alignment and size for type T is obtained, and
— if the object has non-trivial initialization, its initialization is complete.
The constructor in this case is non-trivial just by being user-provided.
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