Can I use a pointer to a destructed object inside its destructor?

Viewed 99

Is it defined behavior that I can access a destroyed object from a pointer inside its destructor? Is the object guaranteed to still be at that pointer location and accessible? For example, in the code below, I destroy a1, and inside its destructor I access a1 from a2, which was pointing to a1.

#include <iostream>

class A
{
public:
    A(int* i) : m_i(i) {}
    ~A();
    int* m_i;
};

A* a1;
A* a2;
int x = 0;

A::~A()
{
    *a2->m_i = 1;
}

int main()
{
    a1 = new A(&x);
    a2 = a1;
    delete a1;
    std::cout << x << std::endl;
    return 0;
}
4 Answers

This is a good question. The basic answer is that when the A object's destructor starts, the lifetime of the object ends, but it is still legal to access the A object's members (with some restrictions) until the destructor of A completes. The standard references are:

[basic.life]/1:

... The lifetime of an object o of type T ends when: if T is a class type with a non-trivial destructor (15.4), the destructor call starts, ...

[basic.life]/6:

Before the lifetime of an object has started but after the storage which the object will occupy has been allocated41 or, 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 will be or was located may be used but only in limited ways. For an object under construction or destruction, see 15.7. ...

[class.cdtor]/2 (i.e., 15.7/2):

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

Thus, as the access to a2->m_i occurs while the object *a2's destructor is being executed, it is allowed. (Note: A::m_i is a direct member of A, since A has no base classes.)

Note that (as some others have pointed out already), the memory in which the A object is stored will not be deallocated until after the destructor completes. That is how delete expressions work.

The object a1 points to is not destroyed until after the destructor exits. That means you can call any member functions and access and member variables in the destructor.

You're code, as is, is valid.

The delete operator that you use with delete a1; performs the following operations:

  1. It destroys the object pointed to by a1 using its static type, i.e., performs a1.~A()
  2. It uses ::operator delete(a1) to free the memory where object pointed to by a1 used to be located (assuming you did not overload it...).

The destructor that is called in the first step in turn performs the following two operations:

  1. The user-defined code is executed.
  2. All member objects are destroyed (by using their destructors).

In summary, your code is executed first (a1 and a2 are both valid at this point, and the object still exists), then the members are destroyed (now the operation would technically be invalid, but still work as an int typically has a rather boring destructor) and finally the memory is freed (after which it would be very bad to do that operation).

If expression is not a null pointer and the deallocation function is not a destroying delete (since C++20), the delete expression invokes the destructor (if any) for the object that's being destroyed, or for every element of the array being destroyed (proceeding from the last element to the first element of the array).

After that, whether or not an exception was thrown by any destructor, the delete expression invokes the deallocation function: either operator delete (for the first version of the expression) or operator delete[] (for the second version of the expression)

https://en.cppreference.com/w/cpp/language/delete

So the destructor will be called before it is freed.

Related