В следующем тексте я буду различать объекты с областью действия , время разрушения которых статически определяется их областью действия (функции, блоки, классы, выражения) и динамические объекты , точное время уничтожения которого обычно неизвестно до времени выполнения.
Хотя семантика уничтожения объектов класса определяется деструкторами, уничтожение скалярного объекта всегда запрещено.В частности, уничтожение переменной-указателя не уничтожает указателя.
Объекты с областью действия
автоматические объекты
Автоматические объекты (обычно называемые "локальными переменными"") уничтожаются в обратном порядке их определения, когда поток управления покидает область их определения:
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
Если при выполнении функции выдается исключение, все ранее созданные автоматические объекты разрушаютсяперед тем, как исключение распространяется на вызывающего.Этот процесс называется разматывание стека .Во время разматывания стека никакие дальнейшие исключения не могут оставлять деструкторы вышеупомянутых ранее созданных автоматических объектов.В противном случае вызывается функция std::terminate
.
Это приводит к одному из самых важных указаний в C ++:
Разрушители никогда не должны бросать.
нелокальные статические объекты
Статические объекты, определенные в области пространства имен (обычно называемые «глобальными переменными»), и члены статических данных разрушаются в обратном порядке их определения после выполнения main
:
struct X
{
static Foo x; // this is only a *declaration*, not a *definition*
};
Foo a;
Foo b;
int main()
{
} <--- y, x, b and a are destructed here
Foo X::x; // this is the respective definition
Foo y;
Обратите внимание, что относительный порядок построения (и уничтожения) статических объектов, определенных в разных единицах перевода, не определен.
Если исключение покидает деструктор статического объекта, функцияstd::terminate
вызывается.
локальные статические объекты
Статические объекты, определенные внутри функций, создаются, когда (и если) поток управления впервые проходит их определение. 1 Они уничтожаются в обратном порядке после выполнения 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(); // note that get_some_Bar is called *first*
get_some_Foo().do_something();
} <--- x and y are destructed here // hence y is destructed *last*
Если исключение оставляет деструктор статическогообъект, вызывается функция std::terminate
.
1: Это чрезвычайно упрощенная модель.Детали инициализации статических объектов на самом деле намного сложнее.
подобъекты базового класса и подобъекты-члены
Когда поток управления покидает тело деструктора объекта, его подобъекты-члены (также известные каккак его "члены данных") уничтожаются в обратном порядке их определения.После этого его подобъекты базового класса уничтожаются в порядке, обратном списку базовых спецификаторов:
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
Если при создании выдается исключение одного из Foo
's подобъектов, то все его ранее построенные подобъекты будут уничтожены до распространения исключения.Деструктор Foo
, с другой стороны, будет не выполняться, поскольку объект Foo
никогда не был полностью построен.
Обратите внимание, что тело деструктора не отвечает за разрушениесами члены данных.Деструктор нужно писать только в том случае, если элемент данных является дескриптором ресурса, который необходимо освободить при разрушении объекта (например, файла, сокета, соединения с базой данных, мьютекса или кучи памяти).
элементы массива
Элементы массива уничтожаются в порядке убывания.Если исключение выдается во время построения n-го элемента, элементы с n-1 по 0 уничтожаются до распространения исключения.
временные объекты
Временный объект создается, когда вычисляется предварительное выражение типа класса. Наиболее ярким примером выражения prvalue является вызов функции, которая возвращает объект по значению, например T operator+(const T&, const T&)
. При нормальных обстоятельствах временный объект разрушается, когда полное выражение, которое лексически содержит значение prvalue, полностью оценено:
__________________________ full-expression
___________ subexpression
_______ subexpression
some_function(a + " " + b);
^ both temporary objects are destructed here
Вышеупомянутый вызов функции some_function(a + " " + b)
является полным выражением, поскольку оно не является частью большего выражения (вместо этого оно является частью выражения-выражения). Следовательно, все временные объекты, которые создаются во время вычисления подвыражений, будут уничтожены в точке с запятой. Существует два таких временных объекта: первый создается во время первого добавления, а второй создается во время второго добавления. Второй временный объект будет разрушен до первого.
Если во время второго добавления возникает исключение, первый временный объект будет уничтожен должным образом перед распространением исключения.
Если локальная ссылка инициализируется выражением prvalue, время жизни временного объекта расширяется до области локальной ссылки, поэтому вы не получите висячую ссылку:
{
const Foo& r = a + " " + b;
^ first temporary (a + " ") is destructed here
// ...
} <--- second temporary (a + " " + b) is destructed not until here
Если вычисляется выражение prvalue не-классового типа, результатом будет значение , а не временный объект. Однако временный объект будет создан , если значение prvalue используется для инициализации ссылки:
const int& r = i + j;
Динамические объекты и массивы
В следующем разделе destroy X означает «сначала уничтожить X, а затем освободить основную память».
Аналогично, create X означает «сначала выделите достаточно памяти, а затем создайте X там».
динамические объекты
Динамический объект, созданный с помощью p = new Foo
, уничтожается с помощью delete p
. Если вы забудете delete p
, у вас есть утечка ресурсов. Никогда не пытайтесь выполнить одно из следующих действий, поскольку все они ведут к неопределенному поведению:
- уничтожить динамический объект с помощью
delete[]
(обратите внимание на квадратные скобки), free
или любым другим способом
- уничтожить динамический объект несколько раз
- доступ к динамическому объекту после его уничтожения
Если во время создания динамического объекта создается исключение *1123*, базовая память освобождается до распространения исключения.
(Деструктор не будет выполнен до освобождения памяти, потому что объект никогда не был полностью построен.)
динамические массивы
Динамический массив, созданный с помощью p = new Foo[n]
, уничтожается с помощью delete[] p
(обратите внимание на квадратные скобки). Если вы забудете delete[] p
, у вас есть утечка ресурсов. Никогда не пытайтесь выполнить одно из следующих действий, поскольку все они ведут к неопределенному поведению:
- уничтожить динамический массив с помощью
delete
, free
или любым другим способом
- уничтожить динамический массив несколько раз
- доступ к динамическому массиву после его уничтожения
Если исключение выдается во время конструкции n-го элемента, элементы с n-1 по 0 уничтожаются в порядке убывания, освобождается основная память и распространяется исключение.
(Обычно вы предпочитаете std::vector<Foo>
над Foo*
для динамических массивов. Это значительно облегчает написание корректного и надежного кода.)
умные указатели подсчета ссылок
Динамический объект, управляемый несколькими std::shared_ptr<Foo>
объектами, уничтожается при уничтожении последнего std::shared_ptr<Foo>
объекта, участвующего в совместном использовании этого динамического объекта.
(Обычно вы предпочитаете std::shared_ptr<Foo>
над Foo*
для общих объектов. Это значительно облегчает написание правильного и надежного кода.)