Почему деструктор называется, если он удаляется и не вызывается, если он не удаляется?
Рассмотрим следующий код:
#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, однако хорошее программирование/логика должно предотвратить необходимость того, что деструктор вообще недоступен).
Сообщите мне, если вам нужна дополнительная информация, или если какое-либо из вышеперечисленных требований нуждается в разъяснении, и я буду очень рад помочь:)