Деструкторы
Деструктор — это специальная функция-член, которая вызывается, когда заканчивается время жизни объекта. Назначение деструктора — освободить ресурсы, которые объект мог получить за время своего существования.
| Деструктор не может быть корутиной. | (начиная с C++20) |
Синтаксис
Деструкторы(до C++20)Предполагаемые деструкторы(начиная с C++20) объявляются с помощью деклараторов функций-членов следующей формы:
class-name-with-tilde ( parameter-list (необязательно) ) except (необязательно) attr (необязательно) |
| class-name-with-tilde | - | выражение-идентификатор идентификатора, возможно, за которым следует список атрибутов, и(начиная с C++11) возможно, заключённое в пару круглых скобок | ||||||
| parameter-list | - | список параметров (должен быть пустым или содержать void) | ||||||
| except | - |
|
||||||
| attr | - | (начиная с C++11) список атрибутов |
Единственные спецификаторы, допустимые в спецификаторах объявления объявления предполагаемого(начиная с C++20) деструктора, — этоconstexpr,(начиная с C++11) friend, inline и virtual (в частности, тип возвращаемого значения не допускается).
Выражение-идентификатор в class-name-with-tilde должно иметь одну из следующих форм:
- В объявлении-члене, принадлежащем описанию членов класса или шаблона класса, но не являющемся объявлением-другом:
- Для классов выражение-идентификатор — это
~, за которым следует внедрённое имя класса непосредственно охватывающего класса. - Для шаблонов классов выражение-идентификатор — это
~, за которым следует имя класса, обозначающее текущую инстанциацию(до C++20)внедрённое имя класса(начиная с C++20) непосредственно охватывающего шаблона класса. - В противном случае выражение-идентификатор является квалифицированным идентификатором, конечный неквалифицированный идентификатор которого — это
~, за которым следует внедрённое имя класса, указанного нетерминальными частями квалифицированного идентификатора.
Пояснение
Деструктор неявно вызывается при завершении времени жизни объекта, в том числе при
- завершении программы для объектов со статической длительностью хранения
| (начиная с C++11) |
- завершении области видимости для объектов с автоматической длительностью хранения и временных объектов, время жизни которых было продлено привязкой к ссылке
- выражении delete для объектов с динамической длительностью хранения
- завершении полного выражения для безымянных временных объектов
- раскрутке стека для объектов с автоматической длительностью хранения, когда исключение покидает их блок и не перехватывается.
Деструктор также можно вызвать явно.
Предполагаемый деструкторКласс может иметь один или несколько предполагаемых деструкторов, один из которых выбирается в качестве деструктора класса. Чтобы определить, какой из предполагаемых деструкторов является деструктором, в конце определения класса выполняется разрешение перегрузки среди объявленных в классе предполагаемых деструкторов с пустым списком аргументов. Если разрешение перегрузки завершается неудачей, программа некорректна. Выбор деструктора не приводит к использованию по ODR выбранного деструктора, и выбранный деструктор может быть удалён. Все предполагаемые деструкторы являются специальными функциями-членами. Если для класса #include <cstdio>
#include <type_traits>
template<typename T>
struct A
{
~A() requires std::is_integral_v<T> { std::puts("~A, T is integral"); }
~A() requires std::is_pointer_v<T> { std::puts("~A, T is a pointer"); }
~A() { std::puts("~A, T is anything else"); }
};
int main()
{
A<int> a;
A<int*> b;
A<float> c;
}
Вывод: ~A, T is anything else ~A, T is a pointer ~A, T is integral | (начиная с C++20) |
Потенциально вызываемый деструктор
Деструктор класса T потенциально вызывается в следующих случаях:
- Он вызывается явно или неявно.
- Выражение new создаёт массив объектов типа
T. - Объект результата оператора return имеет тип
T. - Массив проходит агрегатную инициализацию, а тип его элемента —
T. - Объект класса проходит агрегатную инициализацию и имеет член типа
T, гдеTне является типом анонимного объединения. - Потенциально создаваемый подобъект имеет тип
Tв не-делегирующем(начиная с C++11) конструкторе. - Создаётся объект исключения типа
T.
Если потенциально вызываемый деструктор удалён или(начиная с C++11) недоступен из контекста вызова, программа некорректна.
Неявно объявленный деструктор
Если для типа класса не задан объявленный пользователем предполагаемый(начиная с C++20) деструктор, компилятор всегда объявляет деструктор как член класса со спецификаторами inlinepublic.
Как и у любой неявно объявленной специальной функции-члена, спецификация исключений неявно объявленного деструктора указывает на отсутствие исключений, если только деструктор любой потенциально создаваемой базовой части или члена не является потенциально бросающим исключения(начиная с C++17)неявное определение непосредственно не вызывало бы функцию с другой спецификацией исключений(до C++17). На практике неявные деструкторы имеют спецификацию noexcept, если только базовый класс или член не «отравляет» класс своим деструктором с noexcept(false).
Неявно определённый деструктор
Если неявно объявленный деструктор не удалён, компилятор неявно определяет его (то есть генерирует и компилирует тело функции), когда он используется по ODR. Тело такого неявно определённого деструктора пусто.
| Если это удовлетворяет требованиям к деструктору constexpr(до C++23)функции constexpr(начиная с C++23), сгенерированный деструктор является constexpr. | (начиная с C++20) |
Удалённый деструкторНеявно объявленный или явно заданный по умолчанию деструктор класса
| (начиная с C++11) |
Тривиальный деструктор
Деструктор класса T является тривиальным, если выполняются все следующие условия:
- Деструктор неявно объявлен(до C++11)не является предоставленным пользователем(начиная с C++11).
- Деструктор не является виртуальным.
- Все прямые базовые классы имеют тривиальные деструкторы.
| (до C++26) |
| (начиная с C++26) |
Тривиальный деструктор — это деструктор, который не выполняет никаких действий. Время жизни объекта с тривиальным деструктором можно безопасно завершить, не вызывая его деструктор (например, освободив память, занятую объектом), без риска утечки ресурсов. Уничтожение объекта с нетривиальным деструктором без вызова его деструктора не является неопределённым поведением, однако корректная работа программы обычно зависит от этого деструктора. Все типы данных, совместимые с языком C (типы POD), тривиально разрушаемы.
Последовательность уничтожения
Как для определённых пользователем, так и для неявно определённых деструкторов, после выполнения тела деструктора и уничтожения любых автоматических объектов, созданных в его теле, компилятор вызывает деструкторы всех нестатических невариантных членов данных класса в обратном порядке объявления, затем вызывает деструкторы всех прямых невиртуальных базовых классов в обратном порядке создания (которые, в свою очередь, вызывают деструкторы своих членов и базовых классов и так далее), а затем, если этот объект принадлежит наиболее производному классу, вызывает деструкторы всех виртуальных базовых классов.
Даже если деструктор вызывается напрямую (например, obj.~Foo();), оператор return в ~Foo() не возвращает управление вызывающей стороне немедленно: сначала вызываются деструкторы всех этих членов и базовых классов.
Виртуальные деструкторы
Удаление производного объекта через указатель на базовый класс приводит к неопределённому поведению, если деструктор базового класса не является виртуальным:
class Base
{
public:
virtual ~Base() {}
};
class Derived : public Base {};
Base* b = new Derived;
delete b; // safe
Общее практическое правило: деструктор базового класса должен быть либо открытым и виртуальным, либо защищённым и невиртуальным.
Чисто виртуальные деструкторы
Предполагаемый(начиная с C++20) деструктор может быть объявлен чисто виртуальным, например в базовом классе, который необходимо сделать абстрактным, но в котором нет других подходящих функций для объявления чисто виртуальными. У чисто виртуального деструктора должно быть определение, поскольку деструкторы всех базовых классов всегда вызываются при уничтожении производного класса:
class AbstractBase
{
public:
virtual ~AbstractBase() = 0;
};
AbstractBase::~AbstractBase() {}
class Derived : public AbstractBase {};
// AbstractBase obj; // compiler error
Derived obj; // OK
Исключения
Как и любая другая функция, деструктор может завершиться выбросом исключения (обычно для этого его необходимо явно объявить как noexcept(false))(начиная с C++11), однако если этот деструктор вызывается во время раскрутки стека, вместо этого вызывается std::terminate.
Хотя std::uncaught_exceptions иногда можно использовать для обнаружения выполняющейся раскрутки стека, в целом считается плохой практикой допускать завершение любого деструктора выбросом исключения. Тем не менее эту возможность используют некоторые библиотеки, такие как SOCI и Galera 3, полагающиеся на возможность выброса исключений деструкторами безымянных временных объектов в конце полного выражения, в котором создаётся временный объект.
std::experimental::scope_success в Library fundamental TS v3 может иметь потенциально бросающий исключения деструктор, который выбрасывает исключение при нормальном выходе из области видимости, если функция выхода выбрасывает исключение.
Примечания
Прямой вызов деструктора обычного объекта, например локальной переменной, приводит к неопределённому поведению, когда деструктор вызывается повторно в конце области видимости.
В обобщённых контекстах синтаксис вызова деструктора можно использовать с объектом несклассового типа; это называется вызовом псевдодеструктора. См. оператор доступа к членам.
| Макрос проверки поддержки возможностей | Значение | Стандарт | Возможность |
|---|---|---|---|
__cpp_trivial_union | 202502L | (C++26) | Ослабление требований к тривиальности специальных функций-членов объединений |
Пример
#include <iostream>
struct A
{
int i;
A(int num) : i(num)
{
std::cout << "ctor a" << i << '\n';
}
(~A)() // but usually ~A()
{
std::cout << "dtor a" << i << '\n';
}
};
A a0(0);
int main()
{
A a1(1);
A* p;
{ // nested scope
A a2(2);
p = new A(3);
} // a2 out of scope
delete p; // calls the destructor of a3
}
Вывод:
ctor a0 ctor a1 ctor a2 ctor a3 dtor a2 dtor a3 dtor a1 dtor a0
Отчёты об ошибках
Следующие отчёты об ошибках, изменяющие поведение, были применены задним числом к ранее опубликованным стандартам C++.
| DR | Применён к | Опубликованное поведение | Корректное поведение |
|---|---|---|---|
| CWG 193 | C++98 | не было указано, уничтожаются ли автоматические объекты в деструкторе до или после уничтожения базовых подобъектов и подобъектов-членов класса | они уничтожаются до уничтожения этих подобъектов |
| CWG 344 | C++98 | синтаксис деклараторa деструктора был некорректным (имел ту же проблему, что и вопрос CWG 194 и вопрос CWG 263) | синтаксис изменён на специализированный синтаксис деклараторa функции |
| CWG 1241 | C++98 | статические члены могли уничтожаться сразу после выполнения деструктора | уничтожаются только нестатические члены |
| CWG 1353 | C++98 | условия, при которых неявно объявленные деструкторы являются некорректными, не учитывали типы многомерных массивов | учитывать эти типы |
| CWG 1435 | C++98 | значение термина «имя класса» в синтаксисе деклараторa деструктора было неясным | синтаксис изменён на специализированный синтаксис деклараторa функции |
| CWG 2180 | C++98 | деструктор класса, который не является наиболее производным классом, вызывал деструкторы своих прямых виртуальных базовых классов | он не будет вызывать эти деструкторы |
| CWG 2807 | C++20 | спецификаторы объявления могли содержать consteval | запрещено |
См. также
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/destructor