Is the destructor called before the constructor is finished?

Suppose I have a class whose constructor creates a thread that removes an 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 is that the destructor can be called before the constructor completes. Is this legal in this case? Since t declared (and therefore initialized) last, and there is no code in the constructor body, and I'm never going to subclass this class, I assume that the object was fully initialized when calling self_destruct . Is this assumption correct?

I know that the delete this; operator delete this; is legal in function members if this not used after this statement. But constructors differ in several ways, so I'm not sure if this works.

In addition, if it is illegal, I’m not sure how to get around it, and the other is spawning a thread in a special initialization function, which should be called after building the object, which I really would like to avoid.

PS: I'm looking for an answer for C ++ 03 (the older compiler for this project is limited to me). std::thread in this example is for illustration purposes only.

+6
source share
4 answers

First, we see that an object of type foo has non-trivial initialization, because its constructor is nontrivial (§3.8 / 1):

It is said that an object has non-trivial initialization if it belongs to a class or an aggregate, and it or one of its members is initialized by a constructor other than the default trivial constructor.

Now we see that an object of type foo lifetime begins after the constructor completes (§ 3.8 / 1):

The lifetime of an object of type T begins when:

  • stores with proper alignment and size for type T and
  • If the object has non-trivial initialization, its initialization is complete.

Now this behavior is undefined if you delete object before the end of the constructor, if the type foo has a nontrivial destructor (§3.8 / 5):

Before the lifetime of the object was started, but after the storage that will occupy the object was allocated [...], any pointer that refers to the storage location in which the object was or was located, but only in limited paths. For the construction or destruction of an object, see 12.7. Otherwise, [...]

So, since our object is under development, we consider §12.7:

Member functions, including virtual functions (10.3), can be called during construction or destruction (12.6.2).

This means that when creating the self_destruct object self_destruct this is normal. However, this section does not specifically say anything about the destruction of an object during its construction. Therefore, I suggest looking at the work of delete-expression .

First, it "will call the destructor (if any) for the object [...] to be deleted." A destructor is a special case of a member function, so it can be called. However, §12.4 Destructors do not say anything about whether it is well defined when the destructor is called during construction. No luck here.

Secondly, "expression-expression will call the release function" and "release function will free the storage location referenced by". Again, nothing is said about how to do this for storage, which is currently being used as an object under construction.

Therefore, I argue that this behavior is undefined in that the standard did not define it very accurately.

Just note: the lifetime of an object of type foo ends when you start the call to the destructor, because it has a nontrivial destructor. Therefore, if delete this; It occurs until the end of the construction of the object, its life time ends before it is launched . It plays with fire.

+7
source

I believe it is clearly defined as illegal (although it can obviously still work with some compilers).

This is about the same situation as "a destructor that is not called when an exception is thrown from the constructor."

Deletion-expression, according to the standard, destroys the most derived object (1.8) or the array created by the new expression (5.3.2). Until the end of the constructor, an object is not the most derived object, but an object of its type as a direct ancestor.

There is no base class in your foo class, so there is no ancestor, therefore this not of type, and your object is not an object at all during delete . But even if there was a base class, the object would not be the most derived object (still making it illegal), and the wrong constructor is called.

+2
source

delete this; works in practice on almost all platforms; some may even guarantee proper behavior as an extension for a particular platform. But IIRC, it is not defined in accordance with the standard.

The behavior you rely on is that you can often call a non-virtual non-stationary member function on a dead object if that member function does not actually have access to this . But this behavior is not permitted by the Standard; it is not tolerated at best.

Section 3.8p6 of the Standard makes the behavior undefined if the object does not live during a call to a non-static member function:

Similarly, before the life time of an object began, but after the storage that will occupy the object was allocated, or after the life of the object expired and before the storage that the object is occupied, reused or released, any value glvalue which refers to the source object, but can only be used in a limited way. For the construction or destruction of an object, see 12.7. Otherwise, this glvalue value refers to the allocated storage and the use of glvalue properties, which are independent of its value, are clearly defined. A program has undefined behavior if:

  • the lvalue-to-rvalue conversion applies to such a glvalue,
  • glvalue is used to access a non-static data element or to call a non-static member function of an object, or
  • glvalue is implicitly converted to a reference to the type of the base class or
  • glvalue is used as the operand of static_cast , unless the conversion is ultimately prior to cv char& or cv unsigned char& , or
  • glvalue is used as an operand of a dynamic_cast or as an operand of typeid .

In this specific case (deleting an object under construction) we find in section 5.3.5p2:

... In the first alternative (to delete an object), the value of the delete operand can be the value of a null pointer, a pointer to an object without an array created by the previous new expression, or a pointer to a subobject representing the base class of such an object (Section 10). If not, the behavior is undefined. In the second alternative (delete an array), the value of the delete operand may be a null pointer value or a pointer value that was the result of a previous new expression. If not, the behavior is undefined.

This requirement is not met. *this is not an object created, past tense, by a new expression. This is an object being created (there is a progressive one). And this interpretation is supported by the case of the array, where the pointer must be the result of the previous new expression ... but the new expression is not yet fully evaluated; it is not the previous one, and it has no result so far.

+1
source

Formally, the object does not exist until the constructor completes successfully. Part of the reason is that the constructor can be called from the constructor of the derived class. In this case, of course, you do not want to destroy the constructed sub-object through an explicit call to the destructor and even less call UB, calling delete this on the (partially) incompletely constructed object.


Standard on the existence of the object, added emphasis:

C ++ 11 §3.8 / 1 :
The lifetime of an object is a property of the runtime of an object. They say that an object has non-trivial initialization if it belongs to a class or an aggregate, and it or one of its members is initialized by a constructor other than the default trivial constructor. [Note: initialization with the trivial copy / move constructor is non-trivial initialization. -end note] The lifetime of an object of type T begins when:
- received storage with proper alignment and size for type T and
- if the object has non-trivial initialization, its initialization is completed .

The constructor in this case is non-trivial, just providing the user.

+1
source

All Articles