In the following text, I will distinguish between objects with a region whose destruction time is statically determined by their covering region (functions, blocks, classes, expressions) and dynamic objects, the exact destruction time of which is usually unknown until at runtime.
While the semantics of the destruction of class objects are determined by destructors, the destruction of a scalar object is always no-op. In particular, destroying a pointer variable does not destroy pointee.
Objects with a scope
auto objects
Automatic objects (usually called "local variables") are destroyed in the reverse order of their definition when the control flow goes beyond their definition:
void some_function() { Foo a; Foo b; if (some_condition) { Foo y; Foo z; } <--- z and y are destructed here } <--- b and a are destructed here
If an exception is thrown during the execution of the function, all previously constructed automatic objects are destroyed before the exception is passed to the caller. This process is called stack expansion. During the unwinding of the stack, no additional exceptions can leave the destructors of the aforementioned previously constructed automatic objects. Otherwise, the std::terminate function is called.
This leads to one of the most important principles in C ++:
Destructors should never throw.
nonlocal static objects
Static objects defined in a namespace area (commonly called "global variables") and static data members are destroyed in the reverse order of their definition after main :
struct X { static Foo x;
Please note that the relative order of construction (and destruction) of static objects defined in different translation units is undefined.
If the exception leaves the static object's destructor, the std::terminate function is called.
local static objects
Static objects defined inside functions are constructed when (and if) the control flow passes through their definition for the first time. 1 They are destroyed in the reverse order after main :
Foo& get_some_Foo() { static Foo x; return x; } Bar& get_some_Bar() { static Bar y; return y; } int main() { get_some_Bar().do_something();
If the exception leaves the static object's destructor, the std::terminate function is called.
1: This is an extremely simplified model. The details of initializing static objects are actually much more complicated.
base class subobjects and member subobjects
When the control flow leaves the body of the object’s destructor, its subobjects (also known as its “data members”) are destroyed in the reverse order of their definition. After that, the base class subobjects are destroyed in the reverse order of the base specifier-list:
class Foo : Bar, Baz { Quux x; Quux y; public: ~Foo() { } <--- y and x are destructed here, }; followed by the Baz and Bar base class subobjects
If an exception occurs during the construction of one of the Foo subobjects, then all its previously constructed subobjects will be destroyed before the exception is propagated. On the other hand, the Foo destructor will not execute because the Foo object was never completely constructed.
Note that the body of the destructor is not responsible for the destruction of the data elements themselves. You only need to write a destructor if the data element is a handle to the resource that should be released when the object is destroyed (for example, a file, socket, database connection, mutex or a lot of memory).
array elements
Elements of the array are destroyed in descending order. If an exception is thrown during the construction of the nth element, elements n-1 to 0 are destroyed before the exception is propagated.
temporary objects
A temporary object is created when an expression of the class praleue class type is evaluated. The most striking example of a prvalue expression is a function call that returns an object by value, for example T operator+(const T&, const T&) . Under normal circumstances, a temporary object is destroyed when a complete expression that lexically contains the value of prvalue is fully evaluated:
__________________________ full-expression ___________ subexpression _______ subexpression some_function(a + " " + b); ^ both temporary objects are destructed here
The above function call some_function(a + " " + b) is a complete expression because it is not part of a larger expression (instead, it is part of an expression-expression). Therefore, all temporary objects that are created when evaluating subexpressions will be destroyed to a semicolon. There are two such temporary objects: the first during the first addition, the second during the second addition. The second temporary object will be destroyed before the first.
If an exception is thrown during the second add, the first first object will be destroyed properly before throwing the exception.
If the local link is initialized with the prvalue expression, the lifetime of the temporary object extends to the local link area, so you will not get a date link:
{ const Foo& r = a + " " + b; ^ first temporary (a + " ") is destructed here // ... } <--- second temporary (a + " " + b) is destructed not until here
If a non-class prvalue expression is evaluated, the result is a value, not a temporary object. However, a temporary object will be created if prvalue is used to initialize the link:
const int& r = i + j;
Dynamic objects and arrays
In the next section, destroying X means "destroy X first and then free up base memory." Similarly, creating X means "first allocate enough memory, and then build X there."
dynamic objects
A dynamic object created with p = new Foo is destroyed via delete p . If you forget delete p , you will have a resource leak. You should never try to do one of the following, as they all lead to undefined behavior:
- destroy a dynamic object via
delete[] (note the square brackets), free or any other means - destroy a dynamic object several times
- access to a dynamic object after its destruction
If an exception occurs during the construction of a dynamic object, the underlying memory is freed before the exception is thrown. (The destructor will not be executed until the memory is freed, because the object was never completely constructed.)
dynamic arrays
A dynamic array created using p = new Foo[n] is destroyed via delete[] p (note the square brackets). If you forget delete[] p , you will have a resource leak. You should never try to do one of the following, as they all lead to undefined behavior:
- destroy a dynamic array via
delete , free or any other means - destroy dynamic array several times
- access to the dynamic array after its destruction.
If an exception is thrown when constructing the nth element, elements n-1 to 0 are destroyed in descending order, the base memory is freed, and the exception is thrown.
(Typically, for dynamic arrays, std::vector<Foo> over Foo* usually recommended, which greatly simplifies the writing of correct and reliable code.)
smart pointer links
A dynamic object managed by several std::shared_ptr<Foo> objects is destroyed during the destruction of the last std::shared_ptr<Foo> object participating in the sharing of this dynamic object.
(Normally, you prefer std::shared_ptr<Foo> over Foo* for shared objects, which greatly simplifies writing correct and reliable code.)