Ответ 1
В следующем тексте я буду различать объекты с областью, время разрушения которых статически определяется их охватывающей областью (функциями, блоками, классами, выражениями) и динамическими объектами, точное время уничтожения которых обычно неизвестно до тех пор, пока во время выполнения.
В то время как семантика разрушения объектов класса определяется деструкторами, уничтожение скалярного объекта всегда является no-op. В частности, уничтожение указательной переменной не разрушает pointee.
Объекты с областью
автоматические объекты
Автоматические объекты (обычно называемые "локальными переменными" ) разрушаются в обратном порядке их определения, когда поток управления выходит за пределы их определения:
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
.
Это приводит к одному из наиболее важных принципов в С++:
Деструкторы никогда не должны бросать.
нелокальные статические объекты
Статические объекты, определенные в области пространства имен (обычно называемые "глобальными переменными" ) и статические члены данных, разрушаются в обратном порядке их определения после выполнения 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;
Обратите внимание, что относительный порядок построения (и разрушения) статических объектов, определенных в разных единицах перевода, равен undefined.
Если исключение оставляет деструктор статического объекта, вызывается функция 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
возникает исключение, то все его ранее построенные подобъекты будут уничтожены до того, как будет распространено исключение. С другой стороны, деструктор Foo
не будет выполнен, поскольку объект Foo
никогда не был полностью сконструирован.
Обратите внимание, что тело деструктора не несет ответственности за разрушение самих элементов данных. Вам нужно только написать деструктор, если элемент данных является дескриптором ресурса, который должен быть выпущен при уничтожении объекта (например, файл, сокет, соединение с базой данных, мьютекс или куча памяти).
элементы массива
Элементы массива разрушаются в порядке убывания. Если во время построения n-го элемента создается исключение, элементы n-1 до 0 уничтожаются до того, как будет распространено исключение.
временные объекты
Временной объект создается, когда оценивается выражение класса praleue типа класса. Наиболее ярким примером выражения 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 типа non-class, результатом является значение, а не временный объект. Тем не менее, временный объект будет создан, если для инициализации ссылки используется prvalue:
const int& r = i + j;
Динамические объекты и массивы
В следующем разделе уничтожение X означает "сначала уничтожить X, а затем освободить базовую память". Аналогично, создание X означает "сначала выделить достаточно памяти, а затем построить X там".
динамические объекты
Динамический объект, созданный с помощью p = new Foo
, уничтожается через delete p
. Если вы забудете delete p
, у вас будет утечка ресурсов. Вы никогда не должны пытаться выполнить одно из следующих действий, поскольку все они приводят к поведению undefined:
- уничтожить динамический объект через
delete[]
(обратите внимание на квадратные скобки),free
или любые другие средства - несколько раз уничтожить динамический объект
- доступ к динамическому объекту после его уничтожения
Если во время построения динамического объекта возникает исключение, базовая память освобождается до того, как будет распространено исключение. (Деструктор не будет выполнен до освобождения памяти, поскольку объект никогда не был полностью сконструирован.)
динамические массивы
Динамический массив, созданный с помощью p = new Foo[n]
, уничтожается через delete[] p
(обратите внимание на квадратные скобки). Если вы забудете delete[] p
, у вас будет утечка ресурсов. Вы никогда не должны пытаться выполнить одно из следующих действий, поскольку все они приводят к поведению undefined:
- уничтожить динамический массив через
delete
,free
или любые другие средства - уничтожить динамический массив несколько раз
- доступ к динамическому массиву после его уничтожения.
Если при построении n-го элемента выбрано исключение, элементы n-1 до 0 уничтожаются в порядке убывания, освобождается базовая память и распространяется исключение.
(Обычно для динамических массивов обычно рекомендуется std::vector<Foo>
over Foo*
, что значительно упрощает запись правильного и надежного кода.)
ссылки на интеллектуальные указатели
Динамический объект, управляемый несколькими объектами std::shared_ptr<Foo>
, уничтожается во время уничтожения последнего std::shared_ptr<Foo>
объекта, участвующего в совместном использовании этого динамического объекта.
(Обычно вы предпочитаете std::shared_ptr<Foo>
через Foo*
для общих объектов, что значительно упрощает запись правильного и надежного кода.)