In the following short example, what can be said about the object the pointer f
points to or used to point to just before returning from main
?
#include <vector>
struct foo {
std::vector<int> m;
};
int main()
{
auto f = new foo;
f->~foo();
}
I believe that there is no longer an object foo
where f
used to point. I've received a lot of comments that this may not be correct and that instead there could be an object foo
in a destroyed, dead or otherwise invalid state.
What does the language standard have to say about the existence of an objects that is explicitly destroyed but whose storage is still valid?
In other words, can it reasonably be said that there is still an object at f
that is outside of its lifetime? Is there such a thing as an object that is not in its lifetime, not begin constructed and not being destructed?
Edit :
It is clear that an object can exist when it isn't in its lifetime. During construction and destruction there is an object and its lifetime has not yet begun or as already ended. From https://timsong-cpp.github.io/cppwp/intro.object#1 :
[...] An object occupies a region of storage in its period of construction ([class.cdtor]), throughout its lifetime, and in its period of destruction ([class.cdtor]). [...]
But after f->~foo();
the object that was pointed to by f
(lets call it o
) is not being constructed, it is not in its lifetime and it is not being destructed. My reading of this section is that o
cannot occupy the storage anymore because it isn't in any of the enumerated situations. It seems like this implies that there is no o
anymore and that there can't be a pointer to o
anymore. By contradiction, if you had a pointer to o
then that pointer would point to storage which o
can't occupy.
Edit 2 :
If there isn't an object anymore, then what kind of value does foo
have? It seems like the only sensible possible value it can have is a pointer to an object, which would contradict the statement. See this question.
In C++, objects essentially are eternal. There's nothing in the language that makes an object disappear. An object that is outside of its lifetime is still an object, it still occupies storage, and the standard has specific things that you can do with a pointer/reference to an object which is outside of its lifetime.
An object only truly goes away when it is impossible to have a valid pointer/reference to it. This happens when the storage occupied by that object ends its storage duration. A pointer to storage that is past its duration is an invalid pointer, even if the address itself later becomes valid again.
So by calling the destructor instead of using delete f
(which would also deallocate the storage), f
remains pointing to an object of type foo
, but that object is outside of its lifetime.
The justification for my above statements basically boils down to the standard having none of the provisions that it would need in order to support the concept of objects being uncreated.
The standard provides clear, unequivocal statements about when an object comes to exist within a piece of storage. [intro.object]/1 outlines the exact mechanisms that provoke the creation of an object.
The standard provides clear, unequivocal statements about when an object's lifetime begins and ends. [basic.life] in its entirely outlines these things, but [basic.life]/1 in particular explains when an object's lifetime begins and ends.
The standard does not provide any statement (clear or otherwise) about when an object no longer exists. The standard says when objects are created, when their lifetimes begin, and when they end. But never does it say when they stop existing within a piece of storage.
There has also been discussion about statements of the form:
any pointer that represents the address of the storage location where the object will be or was located may be used but only in limited ways.
Emphasis added.
The use of the past-tense suggests that the object is no longer located in that storage. But when did the object stop being located there? There is no clear statement about what exactly caused that to happen. And without that, the use of past-tense here just doesn't matter.
If you can't point to a statement about when it stopped being there, then the absolute most you can say is that there are a couple of places in the standard with wording that could be cleaned up. It doesn't undo the clear fact that the standard does not say when objects stop existing.
But it does say when objects are no longer accessible.
In order for an object to cease existing, the standard would have to account for pointers which point to those objects when they no longer exist. After all, if a pointer is pointing to an object, then that object must still exist, right?
[basic.compound]/3 outlines the states that a pointer can have. Pointers can be in one of four states:
- a pointer to an object or function (the pointer is said to point to the object or function), or
- a pointer past the end of an object ([expr.add]), or
- the null pointer value ([conv.ptr]) for that type, or
- an invalid pointer value.
There is no allowance given for a pointer which points to no object. There is an allowance for an "invalid pointer value", but pointers only become invalid when the storage duration for the storage they point into ends:
When the end of the duration of a region of storage is reached, the values of all pointers representing the address of any part of that region of storage become invalid pointer values.
Note that this statement means that all pointers to such objects cease being in the "pointer to object" state and enter the "invalid pointer" state. Thus, objects within such storage (both within and outside of their lifetimes) stop being accessible.
This is exactly the sort of statement that would need to exist for the standard to support the concept of objects no longer existing.
But no such statement exists.
[basic.life] does have several statements that address limited ways that pointers to objects outside of their lifetime can be used. But note the specific wording it uses:
For an object under construction or destruction, see [class.cdtor]. Otherwise, such a pointer refers to allocated storage ([basic.stc.dynamic.deallocation]), and using the pointer as if the pointer were of type void*, is well-defined.
It never says that the pointer "points to" allocated storage. It never undoes [basic.compound]/3's declaration about the kinds of pointers. The pointer is still a pointer to an object; it's just that the pointer "refers to allocated storage". And that the pointer can be used as a void*
.
That is, there's no such thing as a "pointer to allocated storage". There is a "pointer to an object outside of its lifetime, whose pointer value can be used to refers to allocated storage". But is still a "pointer to an object".
Objects must exist in order to have a lifetime. The standard makes that clear. However, the standard does not at any point link the existence of an object to its lifetime.
Indeed, the object model would be a lot less complicated if ending the lifetime of an object meant that the object didn't exist. Most of [basic.life] is about carving out specific ways you can use the name of an object or a pointer/reference to it outside of the lifetime of that object. We wouldn't need that sort of stuff if the object itself didn't exist.
Stated in discussion about this matter was this:
I believe mentions of out-of-lifetime objects are there to account for objects that are being constructed and objects that are being destructed.
If that were true, what is [basic.life]/8 talking about with this statement:
If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object
If pointers to the original object become pointers to allocated memory when the object's lifetime ends, why does this statement talk about pointers to the original object? Pointers can't point to objects that don't exist because they don't exist.
This passage can only make sense if those objects continue to exist outside of their lifetimes. And no, it's not just about within the constructor/destructor; the example in the section makes that abundantly clear:
struct C {
int i;
void f();
const C& operator=( const C& );
};
const C& C::operator=( const C& other) {
if ( this != &other ) {
this->~C(); // lifetime of *this ends
new (this) C(other); // new object of type C created
f(); // well-defined
}
return *this;
}
C c1;
C c2;
c1 = c2; // well-defined
c1.f(); // well-defined; c1 refers to a new object of type C
While operator=
does call the destructor, that destructor finishes before the this
pointer is used. Thus, the special provisions of of [class.cdtor] does not apply to this
at the moment the new object is created. So the new object is created outside of the destructor call to the old one.
So it's very clear that the "outside its lifetime" rules for objects are meant to always work. It's not just a provision for constructors/destructors (if it was, it would explicitly call that out). This means that names/pointers/references must still name/point-to/reference objects outside of their lifetime until the creation of the new object.
And for that to happen, the object they name/point-to/reference must still exist.
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