Namespaces
Variants

Destructors

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Деструктор — это специальная функция-член , которая вызывается при завершении времени жизни объекта . Цель деструктора — освободить ресурсы, которые объект мог приобрести в течение своего времени жизни.

Деструктор не может быть корутиной .

(since C++20)

Содержание

Синтаксис

Деструкторы (до C++20) Проспективные деструкторы (начиная с C++20) объявляются с использованием деклараторов функций-членов следующего вида:

имя-класса-с-тильдой ( список-параметров  (опционально) ) except  (опционально) attr  (опционально)
class-name-with-tilde - идентификаторное выражение, возможно, за которым следует список атрибутов , и (начиная с C++11) возможно, заключенное в пару круглых скобок
parameter-list - список параметров (должен быть либо пустым, либо void )
except -

спецификация динамических исключений

(до C++11)

либо спецификация динамических исключений
либо спецификация noexcept

(начиная с C++11)
(до C++17)

спецификация noexcept

(начиная с C++17)
attr - (начиная с C++11) список атрибутов

Единственные спецификаторы, разрешённые в спецификаторах объявления для проспективного (начиная с C++20) деструктора, это constexpr , (начиная с C++11) friend , inline и virtual (в частности, возвращаемый тип не допускается).

Идентификаторное выражение class-name-with-tilde должно иметь одну из следующих форм:

  • Для классов идентификаторное выражение представляет собой ~ за которым следует injected-class-name непосредственно охватывающего класса.
  • Для шаблонов классов идентификаторное выражение представляет собой ~ за которым следует имя класса, которое обозначает current instantiation (до C++20) injected-class-name (начиная с C++20) непосредственно охватывающего шаблона класса.
  • В противном случае, идентификаторное выражение является квалифицированным идентификатором, терминальный неквалифицированный идентификатор которого представляет собой ~ за которым следует injected-class name класса, указанного не-терминальными частями квалифицированного идентификатора.

Объяснение

Деструктор неявно вызывается всякий раз, когда время жизни объекта завершается, что включает

  • завершение потока, для объектов с продолжительностью хранения в потоке
(since C++11)
  • конец области видимости, для объектов с автоматической продолжительностью хранения и для временных объектов, время жизни которых было продлено привязкой к ссылке
  • delete expression , для объектов с динамической продолжительностью хранения
  • конец полного expression , для безымянных временных объектов
  • stack unwinding , для объектов с автоматической продолжительностью хранения при выходе исключения за пределы их блока, не перехваченного

Деструктор также может быть вызван явно.

Перспективный деструктор

Класс может иметь один или несколько перспективных деструкторов, один из которых выбирается в качестве деструктора для класса.

Для определения того, какой перспективный деструктор является деструктором, в конце определения класса разрешение перегрузки выполняется среди перспективных деструкторов, объявленных в классе с пустым списком аргументов. Если разрешение перегрузки завершается неудачей, программа является некорректной. Выбор деструктора не odr-использует выбранный деструктор, и выбранный деструктор может быть удаленным.

Все перспективные деструкторы являются специальными функциями-членами. Если для класса T не предоставлен пользовательский перспективный деструктор, компилятор всегда неявно объявляет один, и неявно объявленный перспективный деструктор также является деструктором для T .

#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 потенциально вызывается в следующих ситуациях:

Если потенциально вызываемый деструктор удален или (начиная с 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)


Удаленный деструктор

Неявно объявленный или явно заданный по умолчанию деструктор для класса T определяется как удаленный, если выполняется любое из следующих условий:

  • удален или недоступен из деструктора T , или
  • в случае, если подобъект является вариантым членом , является нетривиальным.
(до C++26)
  • T не является объединением и имеет не- вариантный потенциально конструируемый подобъект классового типа M (или возможно многомерный массив таких), для которого M имеет деструктор, который удален или недоступен из деструктора T .
  • T является объединением, и выполняется любое из следующих условий:
  • Разрешение перегрузки для выбора конструктора для инициализации по умолчанию объекта типа T либо завершается неудачей, либо выбирает конструктор, который либо удален, либо нетривиален.
  • T имеет вариантный член V классового типа M (или возможно многомерный массив таких), где V имеет инициализатор по умолчанию и M имеет нетривиальный деструктор.
(начиная с C++26)
  • неоднозначности, или
  • функции, которая удалена или недоступна из деструктора.

Явно заданный по умолчанию предполагаемый деструктор для T определяется как удаленный, если он не является деструктором для T .

(начиная с C++20)
(начиная с C++11)

Тривиальный деструктор

Деструктор класса T является тривиальным, если выполняются все следующие условия:

  • Деструктор является неявно-объявленным (до C++11) не предоставленным пользователем (начиная с C++11) .
  • Деструктор не является виртуальным.
  • Все прямые базовые классы имеют тривиальные деструкторы.
  • Каждый нестатический член данных классового типа (или массив классового типа) имеет тривиальный деструктор.
(до C++26)
  • Либо T является объединением, либо каждый вариант нестатического члена данных классового типа (или массив классового типа) имеет тривиальный деструктор.
(начиная с 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 запрещено

Смотрите также