I don't understand this:
3.8/1 "The lifetime of an object of type T ends when: — if T is a class type with a non-trivial destructor (12.4), the destructor call
starts, or — the storage which the object occupies is reused or
released."
If the lifetime ends before the destructor starts, doesn't that mean accessing members in the destructor is undefined behavior?
I saw this quote too:
12.7 "For an object with a non-trivial destructor, referring to any non-static member or base class of the object after the destructor
finishes execution results in undefined behavior."
But it doesn't make clear what's allowed during the destructor.
If the lifetime ends before the destructor starts, doesn't that mean accessing members in the destructor is undefined behavior?
Hopefully not:
From N3242 Construction and destruction [class.cdtor] /3
To form a pointer to (or access the value of) a direct non-static member of an object obj, the construction of obj shall have started and its destruction shall not have completed, otherwise the computation of the pointer value (or accessing the member value) results in undefined behavior.
The "lifetime" of an object is relevant for consumers of the object, not the object itself. Therefore a consuming class should not attempt to access members of an object once destruction has started.
No, there's no problem:
Member objects come alive before a constructor body runs, and they stay alive until after the destructor finishes. Therefore, you can refer to member objects in the constructor and the destructor.
The object itself doesn't come alive until after its own constructor finishes, and it dies as soon as its destructor starts execution. But that's only as far as the outside world is concerned. Constructors and destructors may still refer to member objects.
"Lifetime" doesn't mean that. It is a precisely defined term in the standard that has a variety of implications, but it might not have all the implications that you would think. Members can still be used during construction and destruction, outside code can call member functions, etc, etc.
Granted, it's a bit odd for client code to call member functions concurrently with the destructor, but not unheard of and certainly not disallowed by the language. In particular, std::condition_variable explicitly allows the destructor to be invoked while there are outstanding calls to condition_variable::wait(). It only prohibits new calls to wait() after the destructor starts.
Related
Another question cites the C++ standard:
3.8/1 "The lifetime of an object of type T ends when: — if T is a class type with a non-trivial destructor (12.4), the destructor call
starts, or — the storage which the object occupies is reused or
released."
It would seem that this means accessing members of an object from a destructor is not allowed. However this seems to be wrong, and the truth is something more like what's explained in Kerrek SB's answer:
Member objects come alive before a constructor body runs, and they
stay alive until after the destructor finishes. Therefore, you can
refer to member objects in the constructor and the destructor.
The object itself doesn't come alive until after its own constructor
finishes, and it dies as soon as its destructor starts execution. But
that's only as far as the outside world is concerned. Constructors and
destructors may still refer to member objects.
I'm wondering if in the destructor I can pass the object's address to an outside class, like:
struct Person;
struct Organizer
{
static void removeFromGuestList(const Person& person); // This then accesses Person members
}
struct Person
{
~Person() {
// I'm about to die, I won't make it to the party
Organizer::removeFromGuestList(*this);
}
};
This seems OK to me as I think an object's lifetime lasts until after the destructor finishes, however this part of the above answer has me doubting:
The object itself doesn't come alive until after its own constructor
finishes, and it dies as soon as its destructor starts execution. But
that's only as far as the outside world is concerned. Constructors and
destructors may still refer to member objects.
The C++ Standard does seem to be just a little bit self-contradictory regarding the exact status of class members during execution of the destructor.
However, the following excerpt from this Draft C++ Standard may give some reassurance that your call to the removeFromGuestList function should be safe (bold italics formatting added by me):
15.7 Construction and destruction
1 For an object with a non-trivial constructor, referring to any non-static member or base
class of the object before the constructor begins execution results in
undefined behavior. For an object with a non-trivial destructor,
referring to any non-static member or base class of the object after
the destructor finishes execution results in undefined behavior.
What remains unclear (at least, to me) is whether or not referring to those class members via a reference to the object being destroyed is valid once the destructor has started execution. That is, assuming your Person class has a member, ObjectType a, is referring to person.a in your removeFromGuestList function valid?
On the other hand, rather than passing *this as its argument, if you were to pass each required member as a 'distinct object', then you would be safe; so, redefining that function as, say, removeFromGuestList(const ObjectType& a) (with possible additional arguments) would be completely safe.
The question arose due to a discussion on Reddit, where a user told me, citing the standard's rules on object lifetime:
I'm pretty certain that it is technically UB to access an object, while it is being destructed.
I rely on this, for example, with classes that manage a background thread; I have their destructors signal the thread to exit and wait until it does, and that thread may access the object. Do I need to refactor my code?
No, it's well-defined.
If it were UB to access the object at all while its destructor is executing, that destructor would itself not be able to do anything with its own object. 😜
During execution of your destructor:
bases have not been destructed yet
the "current" object itself has not been destructed yet (and neither have its members)
some resources may have been released, if you did so in your destructor already
derived subobjects have been destructed
virtual function calls will safely refer to the "current" object, rather than these now-dead derived subobjects
dynamic_cast and typeid will do the same
you must not do any of these using a Derived*, though! Via a Base* or Current* is fine
Most of these rules are covered by [class.cdtor].
Though the object's lifetime does technically end with the beginning of the destructor "call", at this point you're in a kind of purgatory where [class.cdtor] takes over with the rules listed above:
[basic.life/7]: [..] after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any glvalue that refers to the original object may be used but only in limited ways. For an object under construction or destruction, see [class.cdtor]. [..]
It's potentially an error-prone and confusing pattern, but it's not inherently incorrect. For your particular use case I'd even call it reasonably conventional.
Is there a way to make a destructor of a class NOT call the destructor of one of the class members and/or NOT call the destructor of its base class?
In case this is not possible, is creating certain class members with placement-new and destructing (/not-destructing) them manually a possible workaround? Thanks!
EDIT:
The reason I need this: Class C owns object M. M has a non-trivial destructor. C is friend of M and manages M in a way that there is no need to call M's destructor. It is OK to call it but it means performance overhead. (it's a problem in this case.)
I was thinking to make an a derived class from M that has a destructor that does nothing, but then that would still call the destructor of the base.
At construction time, C++ ensures that the subclasses contructors are first called, then the members are contructed, and finally the appropriate constructor is applied. At destruction time the symetric is done.
That means that you cannot prevent the application of a base class destructor, nor of any member constructor, as soon as the object is destructed. If you want to only destruct some, you must find a way to not destruct the object (just use a raw pointer...) and manually call destructors on what you want. But you certainly do not want to do that!
C++ is very confident on the programmer skills, so it is easy to write a program invoking undefined behaviour. If you find yourself trying to subvert the C++ compiler to not call the destructor of a base class or of a member, you have a major problem. The member you do not want to destroy should not be a member but more probably a pointer (raw or shared) or a reference to an external object that will have its own lifetime management. And the base class should probably also be a pointer or reference to an external object, and here again the lifetime can (and should) be managed outside of the class.
If the destructor has observable side-effects, then it would be undefined behaviour to end the lifetime of the object without invoking the destructor. This is covered in C++14 [basic.life]/4:
A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type with a non-trivial destructor. For an object of a class type with a non-trivial destructor, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released; however, if there is no explicit call to the destructor or if a delete-expression is not used to release the storage, the destructor shall not be implicitly called and any program that depends on the side effects produced by the destructor has undefined behavior.
So, there is no way to get around it. Perhaps you could redesign your code so that the destructor doesn't execute any unnecessary statements, or something.
[class.dtor]/15 reads, emphasis mine:
Once a destructor is invoked for an object, the object no longer exists; the behavior is undefined if the destructor is invoked for an object whose lifetime has ended (3.8).
However, as far as I can tell, this is the only reference in the standard to an object "existing." This also seems to contrast with [basic.life], which is more specific:
The lifetime of an object of type T ends when:
if T is a class type with a non-trivial destructor (12.4), the destructor call starts, or
the storage which the object occupies is reused or released.
We have two different wordings here: "the lifetime of an object ends" and "the object no longer exists," the former only happens with a non-trivial destructor and the latter happens with any destructor. What is the significance of the difference? What is the implication of an object no longer existing?
The quoted wording would seem to imply that a compiler could correctly insert code that returns the memory associated with an object to the heap at the beginning of its destructor. But doing that would eliminate the ability of an object to reference its own members during destruction, which is required if an object is to be able to destroy itself.
So I think the quoted wording is broken and should be fixed.
Concerning what "lifetime" and "existence" mean, I propose that there are some different contexts, in which they mean different things:
Within the context of construction, lifetime and existence begin when a constructor begins. Outside that context, they begin when a constructor ends.
Within the context of destruction, lifetime and existence end when a destructor ends. Outside that context, they end when destruction begins.
So an object may refer to its own members during construction, and potentially pass itself to functions of other objects, which may refer to the object and its members, and so on. But in general, objects (instances of classes) may not be referenced (without producing undefined behavior) until after one of their constructors has finished.
And an object's destructor may refer to its own members and call functions of other (existing) objects, which may refer to the object being destroyed and/or its members. But in general, an object may not be referenced after its destructor has started.
This sort of multi-contextual definition is what makes the most sense to me, but I can see arguments being made that an object should be considered to be alive from the moment memory is allocated for it to the moment that memory is released, and I would say memory for a shallow copy should be allocated for an object when one of its constructors starts, and released when its destructor ends.
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.
delete this; works correctly in practice on most platforms; some may even guarantee correct behavior as a platform-specific extension. But IIRC it isn't well-defined according to the Standard.
The behavior you're relying on is that it's often possible to call a non-virtual non-static member function on a dead object, as long as that member function doesn't actually access this. But this behavior is not allowed by the Standard; it is at best non-portable.
Section 3.8p6 of the Standard makes it undefined behavior if an object isn't live during a call to a non-static member function:
Similarly, 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 glvalue that refers to the original object may be used but only in limited ways. For an object under construction or destruction, see 12.7. Otherwise, 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:
an lvalue-to-rvalue conversion is applied to such a glvalue,
the glvalue is used to access a non-static data member or call a non-static member function of the object, or
the glvalue is implicitly converted to a reference to a base class type, or
the glvalue is used as the operand of a static_cast except when the conversion is ultimately
to cvchar& or cvunsigned char&, or
the glvalue is used as the operand of a dynamic_cast or as the operand of typeid.
For this specific case (deleting an object under construction), we find in section 5.3.5p2:
... In the first alternative (delete object), the value of the operand of delete may be a null pointer value, a pointer to a non-array object created by a previous new-expression, or a pointer to a subobject representing a base class of such an object (Clause 10). If not, the behavior is undefined. In the second alternative (delete array), the value of the operand of delete may be a null pointer value or a pointer value that resulted from a previous array 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. It is an object being created (present progressive). And this interpretation is supported by the array case, where the pointer must be the result of a previous new-expression... but the new-expression is not yet completely evaluated; it is not previous and it has no result yet.