Spec-Zone.ru › C++

Деструкторы

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

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

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

Синтаксис

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

class-name-with-tilde ( parameter-list (необязательно) ) 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 должно иметь одну из следующих форм:

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

Пояснение

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

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

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

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

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

Чтобы определить, какой из предполагаемых деструкторов является деструктором, в конце определения класса выполняется разрешение перегрузки среди объявленных в классе предполагаемых деструкторов с пустым списком аргументов. Если разрешение перегрузки завершается неудачей, программа некорректна. Выбор деструктора не приводит к использованию по 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 потенциально вызывается в следующих случаях:

  • Он вызывается явно или неявно.
  • Выражение 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)

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

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

  • T имеет потенциально создаваемый подобъект типа класса M (или, возможно, многомерный массив такого типа), для которого M имеет деструктор, который
    • удалён или недоступен из деструктора 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)

Тривиальный деструктор — это деструктор, который не выполняет никаких действий. Время жизни объекта с тривиальным деструктором можно безопасно завершить, не вызывая его деструктор (например, освободив память, занятую объектом), без риска утечки ресурсов. Уничтожение объекта с нетривиальным деструктором без вызова его деструктора не является неопределённым поведением, однако корректная работа программы обычно зависит от этого деструктора. Все типы данных, совместимые с языком 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 запрещено

См. также

  • устранение копирования
  • new
  • delete

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/destructor

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API