What does it mean for an object to exist in C++? - c++

[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.

Related

Is using placement new with variable on the stack is correct?

Let's take a look to this code:
A a(123);
new(&a) A(124);
Test says that in this case when program is shutting down destructor ~A() will called once. So if in A we has some pointers as fields we will get memory leak.
A a(123);
a.~A();
new(&a) A(124);
Here all will be correct. But according to standard using object after destructor calling is undefined behaviour(though mostly compilers provide behaviour without some troubles).
Can I take an address of object which destructor has been called?
Is calling placement new on stack variable is correct operation?
Can I take an address of object which destructor has been called?
[edited:]
Such an address is valid so if you have a pointer to it, it is valid.
Can you take its address after the fact, I am not fully sure.
basic.life.6 […] After the lifetime of an object has ended and before the storage which the object occupied is reused or released, any pointer that represents the address of the storage location where the object was located may be used but only in limited ways. […] Such a pointer refers to allocated storage, and using the pointer as if the pointer were of type void* is well-defined.
Check out the full text for the all the restrictions, but use in placement-new is allowed.
As for the comments, I would argue both your samples are correct within the scope you showed.
A a(123);
new(&a) A(124);
I would argue this, given what we know in the sample, is correct.
Ending the lifetime of an object by re-using its storage is valid as per basic.life.5. Only condition is the program does not depend on side effects produced by the destructor - to be on the safe side I would only do that on trivially destructible types. Otherwise you need an explicit destructor call, like you did there:
A a(123);
a.~A();
new(&a) A(124);
I do not see any rule preventing that. The standard even explicitly mentions when such a construct is invalid:
If a program:
ends the lifetime of an object of type T with static, thread, or
automatic storage duration
and another object of the original type does not occupy that same
storage location when the implicit destructor call takes place,
the behavior of the program is undefined.
(formatting as bullet points mine)
Though not a definite proof, this passage suggests that in other cases, the behavior is defined. I cannot think of another rule that his would violate.
(Do note I use C++20 version of the standard)
Yes, this usage is valid. The storage for an object is independent from the object's lifetime.
By calling the destructor, you are ending the object's lifetime, but that does not mean that the storage is released.
According to the standard, that storage may be reused or released. And what you do is reusing it.
This exact case is well-defined in the standard in basic.life#8
Keep in mind that, because 'a' is a variable with automatic storage, at the end of the scope the destructor for that variable's type will be invoked.

Is it really UB to access an object whose user-defined destructor has started, but not finished?

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.

Why isn't it undefined behaviour to destroy an object that was overwritten by placement new?

I'm trying to figure out whether the following is undefined behaviour. I have a feeling it's not UB, but my reading of the standard makes it look like it is UB:
#include <iostream>
struct A {
A() { std::cout << "1"; }
~A() { std::cout << "2"; }
};
int main() {
A a;
new (&a) A;
}
Quoting the C++11 standard:
basic.life¶4 says "A program may end the lifetime of any object by reusing the storage which the object occupies"
So after new (&a) A, the original A object has ended its lifetime.
class.dtor¶11.3 says that "Destructors are invoked implicitly for constructed objects with automatic storage duration ([basic.stc.auto]) when the block in which an object is created exits ([stmt.dcl])"
So the destructor for the original A object is invoked implicitly when main exits.
class.dtor¶15 says "the behavior is undefined if the destructor is invoked for an object whose lifetime has ended ([basic.life])."
So this is undefined behaviour, since the original A no longer exists (even if the new a now exists in the same storage).
The question is whether the destructor for the original A is called, or whether the destructor for the object currently named a is called.
I am aware of basic.life¶7, which says that the name a refers to the new object after the placement new. But class.dtor¶11.3 explicitly says that it's the destructor of the object which exits scope which is called, not the destructor of the object referred to by a name that exits scope.
Am I misreading the standard, or is this actually undefined behaviour?
Edit: Several people have told me not to do this. To clarify, I'm definitely not planning on doing this in production code! This is for a CppQuiz question, which is about corner cases rather than best practices.
You're misreading it.
"Destructors are invoked implicitly for constructed objects" … meaning those that exist and their existence has gone as far as complete construction. Although arguably not entirely spelled out, the original A does not meet this criterion as it is no longer "constructed": it does not exist at all! Only the new/replacement object is automatically destructed, then, at the end of main, as you'd expect.
Otherwise, this form of placement new would be pretty dangerous and of debatable value in the language. However, it's worth pointing out that re-using an actual A in this manner is a bit strange and unusual, if for no other reason than it leads to just this sort of question. Typically you'd placement-new into some bland buffer (like a char[N] or some aligned storage) and then later invoke the destructor yourself too.
Something resembling your example may actually be found at basic.life¶8 — it's UB, but only because someone constructed a T on top of an B; the wording suggests pretty clearly that this is the only problem with the code.
But here's the clincher:
The properties ascribed to objects throughout this International Standard apply for a given object only during its lifetime. [..] [basic.life¶3]
Am I misreading the standard, or is this actually undefined behaviour?
None of those. The standard is not unclear but it could be clearer. The intent though is that the new object's destructor is called, as implied in [basic.life]p9.
[class.dtor]p12 isn't very accurate. I asked Core about it and Mike Miller (a very senior member) said:
I wouldn't say that it's a contradiction [[class.dtor]p12 vs [basic.life]p9], but clarification is certainly needed. The destructor description was written slightly naively, without taking into consideration that the original object occupying a bit of automatic storage might have been replaced by a different object occupying that same bit of automatic storage, but the intent was that if a constructor was invoked on that bit of automatic storage to create an object therein - i.e., if control flowed through that declaration - then the destructor will be invoked for the object presumed to occupy that bit of automatic storage when the block is exited - even it it's not the "same" object that was created by the constructor invocation.
I'll update this answer with the CWG issue as soon as it is published. So, your code does not have UB.
Too long for a comment.
Lightness' answer is correct and his link is the proper reference.
But let's examine terminology more precisely. There is
"Storage duration", concerning memory.
"Lifetime", concerning objects.
"Scope", concerning names.
For automatic variables all three coincide, which is why we often do not clearly distinguish: A "variable goes out of scope". That is: The name goes out of scope; if it is an object with automatic storage duration, the destructor is called, ending the lifetime of the named object; and finally the memory is released.
In your example only name scope and storage duration coincide — at any point during its existence the name a refers to valid memory — , while object lifetime is split between two distinct objects at the same memory location and with the same name a.
And no, I think you cannot understand "constructed" in 11.3 as "fully constructed and not destroyed" because the dtor will be called again (wrongly) if the object's lifetime was ended prematurely by a preceding explicit destructor call.
In fact, that's one of the concerns with the concept of memory re-use: If construction of the new object fails with an exception the scope will be left and a destructor call will be attempted on an incomplete object, or on the old object which was deleted already.
I suppose you can imagine the automatically allocated, typed memory marked with a tag "to be destroyed" which is evaluated when the stack is unwound. The C++ runtime does not really track individual objects or their state beyond this simple concept. Since variable names are basically constant addresses it is convenient to think of "the name going out of scope" triggering the destructor call on the named object of the supposed type supposedly present at that location. If one of these suppositions is wrong all bets are off.
Imagine using placement new to create a struct B to the storage where the A a objects lives. In the end of the scope, the destructor of the struct A will be called (because the variable a of type A goes out of scope), even if an object of type B is in reallty living there right now.
As already cited:
"If a program ends the lifetime of an object of type T with static
([basic.stc.static]), thread ([basic.stc.thread]), or automatic
([basic.stc.auto]) storage duration and if T has a non-trivial
destructor,39 the program must ensure that an object of the original
type occupies that same storage location when the implicit destructor
call takes place;"
So after putting B into the a storage, you need to destroy B and put an A there again, to not violate the rule above. This somehow not apply here directly, because you are putting an A to an A, but it shows the behavior. It shows, that this thinking is wrong:
So the destructor for the original A object is invoked implicitly when
main exits.
There is no "original" object any longer. There is just an object currently alive in the storage of a. And thats it. And on the data currently sitting in a, a function is called, namely the destructor of A. Thats what the program compiles to. If it would magically kept track of all "original" objects you would somehow have a dynamic runtime behavior.
Additionally:
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
([expr.delete]) 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.
Since the destructor of A is not trivial and has side effects, (i think) its undefined behavior. For build in types, this does not apply (hence you can use a char buffer as object buffer without reconstructing the chars back into the buffer after using it) since they have a trivial (no-op) destructor.

Is it UB to re-use an object's storage without destroying it first?

Given non-POD type T:
auto p = new T();
::new (p) T();
/* ... */
delete p;
This is UB, right?
Clearly I'm not directly leaking the memory allocated for that first T (and if it has no indirect members then I'm not leaking anything at all), but it never got destructed, which seems to me to be a great candidate for spontaneous annihilation of galaxies populated by sentient cat-like beings.
Thanks to #Xeo for, um, "inspiring" this question in the C++ Lounge.
It rather depends.
[C++11: 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.
Clearly, this is a case of re-use.
And:
[C++11: 3.8/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 (5.3.5) 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, even for a non-POD type T, it's valid iff nothing in your program actually relied on what the destructor was doing.
It's a bit airy-fairy, but it does potentially allow what you're doing.
Note that this leniency does not extend to some only slightly more bizarre cases:
[C++11: 3.8/9]: Creating a new object at the storage location that a const object with static, thread, or automatic storage duration occupies or, at the storage location that such a const object used to occupy before its lifetime ended results in undefined behavior

Lifetime of object is over before destructor is called?

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.