Destructors
Деструктор — это специальная функция-член , которая вызывается при завершении времени жизни объекта . Цель деструктора — освободить ресурсы, которые объект мог приобрести в течение своего времени жизни.
|
Деструктор не может быть корутиной . |
(since C++20) |
Синтаксис
Деструкторы (до C++20) Проспективные деструкторы (начиная с C++20) объявляются с использованием деклараторов функций-членов следующего вида:
имя-класса-с-тильдой
(
список-параметров
(опционально)
)
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 должно иметь одну из следующих форм:
- В объявлении члена, которое принадлежит спецификации членов класса или шаблона класса, но не является объявлением friend :
-
- Для классов идентификаторное выражение представляет собой ~ за которым следует injected-class-name непосредственно охватывающего класса.
- Для шаблонов классов идентификаторное выражение представляет собой ~ за которым следует имя класса, которое обозначает current instantiation (до C++20) injected-class-name (начиная с C++20) непосредственно охватывающего шаблона класса.
- В противном случае, идентификаторное выражение является квалифицированным идентификатором, терминальный неквалифицированный идентификатор которого представляет собой ~ за которым следует injected-class name класса, указанного не-терминальными частями квалифицированного идентификатора.
Объяснение
Деструктор неявно вызывается всякий раз, когда время жизни объекта завершается, что включает
- завершение программы , для объектов со static storage duration
|
(since C++11) |
- конец области видимости, для объектов с автоматической продолжительностью хранения и для временных объектов, время жизни которых было продлено привязкой к ссылке
- delete expression , для объектов с динамической продолжительностью хранения
- конец полного expression , для безымянных временных объектов
- stack unwinding , для объектов с автоматической продолжительностью хранения при выходе исключения за пределы их блока, не перехваченного
Деструктор также может быть вызван явно.
Перспективный деструкторКласс может иметь один или несколько перспективных деструкторов, один из которых выбирается в качестве деструктора для класса. Для определения того, какой перспективный деструктор является деструктором, в конце определения класса разрешение перегрузки выполняется среди перспективных деструкторов, объявленных в классе с пустым списком аргументов. Если разрешение перегрузки завершается неудачей, программа является некорректной. Выбор деструктора не 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) недоступен из контекста вызова, программа является некорректной.
Неявно объявленный деструктор
Если для class type не предоставлен пользовательский проспективный (since C++20) деструктор, компилятор всегда объявляет деструктор как inline public член своего класса.
Как и для любой неявно объявленной специальной функции-члена, спецификация исключений неявно объявленного деструктора является non-throwing, если только деструктор любого потенциально сконструированного базового класса или члена не является потенциально выбрасывающим (since C++17) неявное определение напрямую вызывало бы функцию с другой спецификацией исключений (until C++17) . На практике неявные деструкторы являются noexcept , если только класс не "отравлен" базовым классом или членом, чей деструктор объявлен как noexcept ( false ) .
Неявно определённый деструктор
Если неявно объявленный деструктор не удален, он неявно определяется (то есть тело функции генерируется и компилируется) компилятором, когда он odr-used . Этот неявно определенный деструктор имеет пустое тело.
|
Если это удовлетворяет требованиям constexpr деструктора (до C++23) constexpr функции (начиная с C++23) , сгенерированный деструктор является constexpr . |
(начиная с C++20) |
Удаленный деструктор
Неявно объявленный или явно заданный по умолчанию деструктор для класса
|
(начиная с C++11) |
Тривиальный деструктор
Деструктор класса
T
является тривиальным, если выполняются все следующие условия:
- Деструктор является неявно-объявленным (до C++11) не предоставленным пользователем (начиная с C++11) .
- Деструктор не является виртуальным.
- Все прямые базовые классы имеют тривиальные деструкторы.
|
(до C++26) |
|
(начиная с C++26) |
Тривиальный деструктор — это деструктор, который не выполняет никаких действий. Объекты с тривиальными деструкторами не требуют выражения delete и могут быть удалены простым освобождением занимаемой ими памяти. Все типы данных, совместимые с языком C (POD-типы), являются тривиально разрушаемыми.
Последовательность уничтожения
Для как пользовательских, так и неявно определенных деструкторов, после выполнения тела деструктора и уничтожения любых автоматических объектов, выделенных внутри тела, компилятор вызывает деструкторы для всех нестатических невариантных членов-данных класса в обратном порядке их объявления, затем вызывает деструкторы всех прямых невиртуальных базовых классов в обратном порядке конструирования (которые, в свою очередь, вызывают деструкторы своих членов и своих базовых классов, и т.д.), и затем, если этот объект является наиболее производным классом , вызывает деструкторы всех виртуальных базовых классов.
Даже когда деструктор вызывается напрямую (например, obj.~Foo ( ) ; ), оператор return в ~Foo ( ) не возвращает управление вызывающей стороне немедленно: сначала вызываются все деструкторы членов и базовых классов.
Виртуальные деструкторы
Удаление объекта через указатель на базовый класс вызывает неопределённое поведение, если деструктор в базовом классе не является virtual :
class Base { public: virtual ~Base() {} }; class Derived : public Base {}; Base* b = new Derived; delete b; // безопасно
Общее правило гласит, что деструктор базового класса должен быть либо публичным и виртуальным, либо защищённым и невиртуальным .
Чистые виртуальные деструкторы
Деструктор может быть объявлен потенциально (начиная с C++20) чисто виртуальным , например, в базовом классе, который должен быть сделан абстрактным, но не имеет других подходящих функций, которые можно было бы объявить чисто виртуальными. Чисто виртуальный деструктор должен иметь определение, поскольку все деструкторы базовых классов всегда вызываются при уничтожении производного класса:
class AbstractBase { public: virtual ~AbstractBase() = 0; }; AbstractBase::~AbstractBase() {} class Derived : public AbstractBase {}; // AbstractBase obj; // ошибка компилятора Derived obj; // OK
Исключения
Как и любая другая функция, деструктор может завершаться выбрасыванием исключения (что обычно требует его явного объявления noexcept ( false ) ) (since 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)() // но обычно ~A() { std::cout << "dtor a" << i << '\n'; } }; A a0(0); int main() { A a1(1); A* p; { // вложенная область видимости A a2(2); p = new A(3); } // a2 выходит из области видимости delete p; // вызывает деструктор 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 |
синтаксис декларатора деструктора был дефектным (имел ту же
проблему, что и CWG issue 194 и CWG issue 263 |
изменен синтаксис на специализированный
синтаксис декларатора функции |
| CWG 1241 | C++98 |
статические члены могли уничтожаться
сразу после выполнения деструктора |
уничтожать только нестатические
члены |
| CWG 1353 | C++98 |
условия, при которых неявно объявленные деструкторы являются
неопределенными, не учитывали многомерные массивы |
учитывать эти типы |
| CWG 1435 | C++98 |
значение "имени класса" в
синтаксисе декларатора деструктора было неясным |
изменен синтаксис на специализированный
синтаксис декларатора функции |
| CWG 2180 | C++98 |
деструктор класса, который не является наиболее производным классом,
вызывал деструкторы его виртуальных прямых базовых классов |
он не будет вызывать эти деструкторы |
| CWG 2807 | C++20 | спецификаторы объявления могли содержать consteval | запрещено |