Spec-Zone.ru › C++

Деструкторы

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

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

(с C++20)

Синтаксис

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

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

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

Объяснение

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

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

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

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

Потенциальный деструктор

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

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

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

(с C++20)

Неявный деструктор

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

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

Неявно определённый деструктор

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

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

(с C++20)

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

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

  • T имеет потенциально сконструированный подобъект типа класса M (или, возможно, многомерный массив такого типа), такой, что M имеет деструктор,
    • который удалён или недоступен из деструктора T, или
    • в случае, если подобъект является членом-вариантом, является нетривиальным.
  • Деструктор является виртуальным, и поиск функции выделения памяти приводит к
    • неоднозначности, или
    • функции, которая удалена или недоступна из деструктора.

Явно-дефолтированный потенциальный деструктор для T определён как удалённый, если это не деструктор T.

(с C++20)

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

Деструктор класса T является тривиальным, если всё из перечисленного верно:

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

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

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

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

Даже когда деструктор вызывается непосредственно (например, obj.~Foo();), оператор возврата в ~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 в библиотечном фундаментальном TS v3 может иметь потенциально выбрасывающий деструктор, который выбрасывает исключение при нормальном выходе из области видимости, если функция выхода выбрасывает исключение.

Пример

#include <iostream>
 
struct A
{
    int i;
 
    A(int num) : i(num)
    {
        std::cout << "ctor a" << i << '\n';
    }
 
    ~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 синтаксис объявления деструктора был дефектным (имел ту же
проблему, что и CWG issue 194 и CWG issue 263
изменён синтаксис на специализированный
синтаксис объявления функции
CWG 1241 C++98 статические члены могут быть уничтожены
сразу после выполнения деструктора
разрушать только не-
статические члены
CWG 1353 C++98 условия, при которых неявно объявленные деструкторы неопределённы,
не учитывали многомерные массивы типов
учитывать эти типы
CWG 2180 C++98 деструктор класса X вызывает деструкторы
для виртуальных прямых базовых классов X
эти деструкторы не вызываются

См. также

  • исключение копирования
  • new
  • delete

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

Spec-Zone.ru

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