I\'ve always assumed that an object begins and ends its lifetime in the same memory location, but I\'ve recently come across a scenario where I need to be sure. Specifically
Specifically, I'm looking for a guarantee from the standard that no matter what optimizations the compiler performs the address an object is constructed at is the same one that it will have its destructor called from...
and that its destructor is, indeed, guaranteed to be called from that location unless the program is terminating.
The standard guarantees both for automatic variables and static
variables as long as one doesn't do bad things with the objects. However, it does not guarantee either for objects allocated from the free store.
Even for automatic variables, a crafty programmer can subvert the intention through pointer manipulation and explicitly calling the destructor through a pointer.
In addition, the wrong destructor will be called when delete
-ing a base class pointer when the base class does not have a virtual
destructor. This will be a programming error, not the result of intention to subvert.
Example:
struct Base
{
int b;
};
struct Derived : virtual Base
{
float d;
};
int main()
{
{
Derived d1; // Not a problem.
}
{
Derived d1;
Derived* ptr = &d1;
delete ptr; // Bad. The programmer subverts the program.
// Must not use delete.
}
{
Derived* d2 = new Derived; // The destructor does not get called automatically.
}
{
Derived* d2 = new Derived;
delete d2; // OK. The proper destructor gets called.
}
{
Derived* d2 = new Derived;
Base* ptr = d2;
delete ptr; // Programmer error. The wrong destructor gets called.
}
}
As mentioned by Nathan Oliver, the standard states that:
[...] 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]).
Compilers respect this, and there are objects (similar to the one you describe) for which it must hold true. Consider std::mutex
. A mutex cannot be copied or moved, and the reason for this is that it must remain at the same location in memory for the duration of it's lifetime in order to work.
Copy/move elision works by creating the object where it needs to go. It's that simple.
We can see this behavior for ourselves:
#include <iostream>
struct Foo {
Foo() {
std::cout << "I am at " << (void*)this << '\n';
}
// Delete copy and move, to ensure it cannot be moved
Foo(const Foo&) = delete;
Foo(Foo&&) = delete;
};
Foo getFoo() {
return Foo();
}
int main() {
Foo* ptr = new Foo(getFoo());
std::cout << "Foo ptr is at " << (void*)ptr << '\n';
delete ptr;
}
This code outputs:
I am at 0x201ee70
Foo ptr is at 0x201ee70
And we see that Foo
remains at the same location for the duration of it's lifetime, without ever being copied or moved, even though it's being created in dynamically allocated memory.
If a function returns a type that is not trivially copyable, then that function takes an implicit parameter representing the memory address where it's supposed to construct the return value.
What you are looking for is defined in [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]).
This means the address cannot change as long as you can access it.