Пространства имён
Варианты
Действия

Деструкторы

Материал из cppreference.com
< cpp‎ | language
 
 
Язык С++
Общие темы
Управление потоком
Операторы условного выполнения
if
Операторы итерации (циклы)
Операторы перехода
Функции
Объявление функции
Выражение лямбда-функции
Спецификатор inline
Спецификации динамических исключений (до C++17*)
Спецификатор noexcept (C++11)
Исключения
Пространства имён
Типы
Спецификаторы
decltype (C++11)
auto (C++11)
alignas (C++11)
Спецификаторы длительности хранения
Инициализация
Выражения
Альтернативные представления
Литералы
Логические - Целочисленные - С плавающей запятой
Символьные - Строковые - nullptr (C++11)
Определяемые пользователем (C++11)
Утилиты
Атрибуты (C++11)
Types
Объявление typedef
Объявление псевдонима типа (C++11)
Casts
Неявные преобразования - Явные преобразования
static_cast - dynamic_cast
const_cast - reinterpret_cast
Выделение памяти
Классы
Свойства функции класса
explicit (C++11)
static
Специальные функции-элементы
Шаблоны
Разное
 

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

Деструктор не должен быть сопрограммой.

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

Содержание

[править] Синтаксис

~ имя-класса (); (1)
virtual ~ имя-класса (); (2)
последовательность-спецификаторов-объявления (необязательно) ~ имя-класса () = default; (3) (начиная с C++11)
последовательность-спецификаторов-объявления (необязательно) ~ имя-класса () = delete; (4) (начиная с C++11)
атрибуты (необязательно) последовательность-спецификаторов-объявления (необязательно) выражение-идентификатор ( void(необязательно) )
except (необязательно) атрибуты(необязательно) предложение-requires (необязательно);
(5)
1) Типичное объявление предполагаемого (начиная с C++20) деструктора.
2) Виртуальный деструктор обычно требуется в базовом классе.
3) Принудительная генерация деструктора компилятором.
4) Отключение неявного деструктора.
5) Формальный синтаксис объявления предполагаемого (начиная с C++20) деструктора.
последовательность-спецификаторов-объявления friend, inline, virtual, constexpr, consteval (начиная с C++20)
выражение-идентификатор в определении класса символ ~ следует за именем-класса. В шаблоне класса символ ~, за которым следует имя текущего экземпляра шаблона. В области видимости пространства имён или в объявлении friend в другом классе, спецификатор-вложенного-имени, за которым следует символ ~, за которым следует имя-класса, который является тем же классом, что и тот, который именован спецификатором-вложенного-имени. В любом случае имя должно быть фактическим именем класса или шаблона, а не определением типа. Всё выражение-идентификатор может быть заключено в круглые скобки, которые не меняют его значения.
атрибуты (начиная с C++11) необязательная последовательность любого количества атрибутов
except спецификация исключения как в любом объявлении функции

Если спецификация исключения не указана явно, считается, что спецификация исключения будет использоваться неявно объявленным деструктором (смотрите ниже). В большинстве случаев это noexcept(true). Таким образом, бросающий исключение деструктор должен быть явно объявлен noexcept(false).

(начиная с C++11)
предложение-requires (начиная с C++20) предложение-require, которое объявляет связанные ограничения для предполагаемого деструктора, которые должны быть удовлетворены, чтобы предполагаемый деструктор был выбран в качестве деструктора

[править] Объяснение

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

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

Деструктор также может быть вызван напрямую, например чтобы уничтожить объект, созданный с помощью размещающего-new или с помощью функции-элемента аллокатора, такой как std::allocator::destroy(), чтобы уничтожить объект, созданный с помощью аллокатора. Обратите внимание, что вызов деструктора непосредственно для обычного объекта, такого как локальная переменная, вызывает неопределённое поведение при повторном вызове деструктора в конце области видимости.

В универсальных контекстах синтаксис вызова деструктора можно использовать с объектом неклассового типа; это известно как вызов псевдодеструктора: смотрите оператор доступа к элементам.

Предполагаемый деструктор

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

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

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

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

[править] Неявно объявленный деструктор

Если для классового типа (struct, class или union) не указан объявленный пользователем предполагаемый (начиная с C++20) деструктор, компилятор всегда будет объявлять деструктор как inline public элемент своего класса.

Как и в случае любой неявно объявленной специальной функции-элемента, спецификация исключения неявно объявленного деструктора не является генерирующей, если только деструктор любого потенциально сконструированного базового класса или элемента не является потенциально генерирующим исключение (начиная с C++17)неявное определение не вызовет напрямую функцию с другой спецификацией исключения (до C++17). На практике неявные деструкторы являются noexcept, если только класс не "инфицирован" базовым классом или элементом, чей деструктор noexcept(false).

[править] Неявно определённый деструктор

Если неявно объявленный деструктор не удаляется, он неявно определяется (то есть тело функции генерируется и компилируется) компилятором при использовании odr. Этот неявно определённый деструктор имеет пустое тело.

Если это соответствует требованиям деструктора constexpr (до C++23)функции constexpr (начиная с C++23), сгенерированный деструктор является constexpr. (начиная с C++20)

[править] Удалённый деструктор

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

  • T имеет нестатический элемент данных, который не может быть уничтожен (имеет удалённый или недоступный деструктор).
  • T имеет прямой или виртуальный базовый класс, который не может быть уничтожен (имеет удалённые или недоступные деструкторы).
  • T является объединением и имеет вариантный элемент с нетривиальным деструктором.
(начиная с C++11)
  • Неявно объявленный деструктор является виртуальным (поскольку базовый класс имеет виртуальный деструктор), а поиск функции освобождения памяти (operator delete()) приводит к вызову неоднозначной, удалённой или недоступной функции.

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

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

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

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

  • Деструктор не предоставляется пользователем (это означает, что он либо неявно объявлен, либо явно определён по умолчанию в своем первом объявлении).
  • Деструктор не является виртуальным (то есть деструктор базового класса не является виртуальным).
  • Все прямые базовые классы имеют тривиальные деструкторы.
  • Все нестатические элементы данных классового типа (или массива классового типа) имеют тривиальные деструкторы.

Тривиальный деструктор это деструктор, который не выполняет никаких действий. Объекты с тривиальными деструкторами не требуют выражения delete, и от них можно избавиться, просто освободив их память. Все типы данных, совместимые с языком C (типы POD), уничтожаются тривиально.

[править] Последовательность уничтожения

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

Даже когда деструктор вызывается напрямую (например, obj.~Foo();), оператор return в ~Foo() не возвращает управление вызывающей стороне немедленно: он вызывает сначала деструкторы базовых классов и элементов.

[править] Виртуальные деструкторы

Удаление объекта через указатель на базовый класс вызывает неопределённое поведение, если только деструктор в базовом классе не является виртуальным:

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)) (начиная с C++11), однако, если этот деструктор вызывается во время раскрутки стека, вместо этого вызывается std::terminate.

Хотя std::uncaught_exceptions иногда может использоваться для обнаружения процесса раскручивания стека, обычно считается плохой практикой позволять любому деструктору завершать работу, вызывая исключение. Эта функциональность, тем не менее, используется некоторыми библиотеками, такими как SOCI и Galera 3, которые полагаются на способность деструкторов безымянных временных объектов генерировать исключения в конце полного выражения, создающего временный объект.

std::experimental::scope_success в Фундаментальной библиотеке TS версии 3 может иметься деструктор, потенциально вызывающий исключение, который генерирует исключение, когда область видимости завершается нормально, а функция выхода выдаёт исключение.

[править] Пример

#include <iostream>
 
struct A
{
    int i;
 
    A(int num) : i(num)
    {
        std::cout << "конструктор a" << i << '\n';
    }
 
    ~A()
    {
        std::cout << "деструктор a" << i << '\n';
    }
};
 
A a0(0);
 
int main()
{
    A a1(1);
    A* p;
 
    { // вложенная область видимости
        A a2(2);
        p = new A(3);
    } // a2 выходит из области видимости
 
    delete p; // вызывает деструктор a3
}

Вывод:

конструктор a0
конструктор a1
конструктор a2
конструктор a3
деструктор a2
деструктор a3
деструктор a1
деструктор a0

[править] Отчёты о дефектах

Следующие изменения поведения были применены с обратной силой к ранее опубликованным стандартам C++:

Номер Применён Поведение в стандарте Корректное поведение
CWG 193 C++98 не указано, уничтожаются ли автоматические объекты в
деструкторе до или после уничтожения базовых
подобъектов и подобъектов элементов класса
они уничтожаются до уничтожения этих подобъектов
CWG 344 C++98 синтаксис декларатора деструктора был дефектным
(имел ту же проблему, что и CWG проблема 194 и
CWG проблема 263
изменился синтаксис на специализированный
синтаксис декларатора функций
CWG 1241 C++98 статические элементы могут быть уничтожены сразу
после выполнения деструктора
уничтожаются только нестатические элементы
CWG 1353 C++98 условия, при которых неявно объявленные деструкторы
не определены, не учитывали типы многомерных массивов
эти типы рассмотрены
CWG 2180 C++98 деструктор для класса X вызывает деструкторы для
виртуальных прямых базовых классов X
эти деструкторы не вызываются

[править] Смотрите также