Почему деструктор называется, если он удаляется и не вызывается, если он не удаляется?

Рассмотрим следующий код:

#include <iostream>

struct A 
{
    A(){ };
    ~A(){ std::cout << "~A::A()" << std::endl; };
};

struct B: A { };

B *b = new B; //Doesn't produce any side-effect.

int main(){ }

DEMO

Программа не производит никакого вывода, что означает, что деструктор не вызывается. Но если мы заменим тело деструктора спецификатором delete, программа даже не будет компилироваться.

#include <iostream>

struct A 
{
    A(){ };
    ~A() = delete; //{ std::cout << "~A::A()" << std::endl; };
};

struct B: A { };

B *b = new B; //Error: use of deleted function

int main(){ }

DEMO

из-за вызова удаленной функции. Это деструктор, который вызывается в этом случае. Почему существует такая разница?

Это не сработает, даже если мы конструктивно определяем конструктор B:

#include <iostream>

struct A 
{
    A(){ };
    ~A() = delete; //{ std::cout << "~A::A()" << std::endl; };
};

struct B: A 
{ 
    B(){ };
};

B *b = new B;

int main(){ }

DEMO

Ответы

Ответ 1

Применяемые части стандарта (цитирование N4140):

§12.4 [class.dtor]/p11:

Деструктор потенциально вызывается, если он вызывается или указывается в пунктах 5.3.4 и 12.6.2. Программа плохо сформирована, если деструктор, который потенциально вызывается, удаляется или недоступен из контекста вызов.

§12.6.2 [class.base.init]/p10:

В конструкторе без делегирования деструктор для каждого потенциально построенный подобъект типа класса потенциально вызывается (12.4). [ Примечание. Это условие гарантирует, что деструкторы могут быть вызваны для полностью построенных под-объектов в случае возникновения исключения (15.2). -end note]

§12 [специальный]/p5:

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

Так как A является не виртуальной прямой базой B и, следовательно, потенциально сконструированным подобъектом B, его деструктор потенциально вызывается в B::B(), и поскольку этот деструктор удален, программа болен -formed.

См. также CWG-выпуск 1424.

Ответ 2

Проблема заключается в том, что конструктор B удаляется компилятором, так как в противном случае определение по умолчанию будет плохо сформировано. Это потому, что A не имеет деструктора, а конструктор по умолчанию B не может построить B из A, если A не может быть уничтожен. Это ошибка, которую вы получаете, если вы скомпилируете g++:

note: 'B::B()' is implicitly deleted because the default definition would be ill-formed:

или clang++:

error: call to implicitly-deleted default constructor of 'B'

И если вы явно объявляете конструктор B(){}, то он жалуется, потому что он не может уничтожить часть A B из-за удаления A::~A(). Способность B похоронить родителя A проверяется во время компиляции, поэтому вы получаете сообщение об ошибке.

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

Ответ 3

Относительно того, что, по моему мнению, является вашим основным вопросом: почему вы не можете построить a B, хотя это только деструктор A, который не существуют: в конструкторе B компилятор автоматически генерирует код для вызова деструктора A, если есть исключение. Если A для использования в качестве базового класса, он должен иметь доступный деструктор (публичный или защищенный).

В вашем случае, конечно, конструктор B не может выбрасывать, и поэтому A::~A() никогда не будет называться. Но компилятор не всегда может определить, действительно ли это так или нет, и стандарт не требует это даже попробовать. Компилятор должен предположить, что тело B::B() может бросок (после полностью построенного A). И даже если это возможно что B::B() не может выбрасывать и не генерирует код для вызовите деструктор A, это оптимизация, которая не является позволило изменить законность кода.

Ответ 4

На основе первого кода:

#include <iostream>
struct A
{
  A(){ };
  ~A(){ std::cout << "~A::A()" << std::endl; };
};

struct B: A { };

B *b = new B; //Doesn't produce any side-effect.

int main(){ }
  • Объект b объявляется глобальным, поэтому его жизненный цикл до тех пор, пока выполняется программа.
  • Объект b выделяется динамически, поэтому требуется "голая" удаление.

Попробуйте следующее:

#include <iostream>

struct A 
{
    A(){ };
    ~A(){ std::cout << "~A::A()" << std::endl; };
};

struct B: A { };

int main(){
    B b;
}

Ответ 5

С++ 14 ясно говорит об этом:

[C++14: 12.6.2/10]: В конструкторе без делегирования потенциально вызывается деструктор для каждого потенциально сконструированного подобъекта типа класса (12.4). [Примечание. Это условие гарантирует, что деструкторы могут быть вызваны для полностью построенных под-объектов в случае возникновения исключения (15.2). -end note]

В С++ 11 такой формулировки нет как она была добавлена ​​в вопрос 1424, но поскольку этот факт нельзя игнорировать, это также на практике реализованы в реализациях С++ 11 и С++ 98/03.

Итак, хотя вы все еще никогда не вызываете деструктор, наличие наследования означает B() требует, чтобы ~A() был invokable: в основном это означает, что он доступен и не удаляется.

Ответ 6

В ответ на ваш заголовок вопрос: если вы не вызываете delete, то удаление не происходит, хотя память, используемая объектом, может быть освобождена по окончании программы, которая ее создала, что может привести к одному из несколько событий:

  • память помечена как свободная/доступная ОС, так как процесс владения завершен, и память затем снова доступна для ОС/других процессов.
  • вызываются деструкторы для всех объектов, либо неявные деструкторы, либо явно объявленные, когда основной процесс завершается (и память затем снова доступна для ОС/других процессов).
  • Исключительная необработанная сила воли от исходного кода превосходит программную функцию и уничтожает содержимое памяти, выделенное не удаленными объектами, и возвращает память в ОС.

Ну, кроме последнего, вы получаете суть, последнее - просто сентиментальность, чтобы укрепить кодер-эго: D

Единственное условие для этого - осиротевшие объекты (где указатель на ячейку памяти теряется в какой-то момент кода, см. здесь, например,) и объект с удаленным деструктором (см. здесь краткое объяснение): их нельзя удалить, поскольку в первом случае они больше не адресуются, а во втором они ошибочный * объект, поскольку для них нет деструктора (* ошибочный, поскольку он не соответствует стандартному использованию /spec, однако бывают случаи, когда вы можете захотеть удалить объект самостоятельно или до того, как процесс владения завершится, например, особый случай singleton, однако хорошее программирование/логика должно предотвратить необходимость того, что деструктор вообще недоступен).

Сообщите мне, если вам нужна дополнительная информация, или если какое-либо из вышеперечисленных требований нуждается в разъяснении, и я буду очень рад помочь:)