Как компилятор обрабатывает вызовы деструктора базового класса в производном деструкторе?

Просто из любопытства я попытался сделать что-то вроде приведенного ниже примера, чтобы увидеть, выдает ли мне компилятор предупреждение или около того вместо вызова бесконечного цикла, который заканчивается переполнением стека. Я подумал, может быть, есть другое поведение, чем просто вызов обычных функций или методов. Но это не так. Есть ли для этого специальное объяснение или это просто обрабатывается как обычные вызовы функций, поскольку я явно вызываю деструктор базового класса с помощью оператора this?

Пример:

class A {
  virtual ~A();
};

class B : A {
  virtual ~B() { this->~A(); }
};

person Sanane Lan    schedule 22.02.2017    source источник
comment
virtual void ~A(); - это не должно компилироваться, деструкторы ничего не возвращают, а обычное имя метода не может содержать тильду.   -  person yeputons    schedule 22.02.2017
comment
@yeputons верно, простите, это опечатка   -  person Sanane Lan    schedule 22.02.2017
comment
Это вызовет неопределенное поведение, дважды вызвав ~A() (поскольку существует подразумеваемый вызов после выхода из тела ~B())   -  person M.M    schedule 22.02.2017
comment
@MM, так что бесконечный цикл - это просто случайное поведение и может быть чем угодно.   -  person Sanane Lan    schedule 22.02.2017
comment
@SananeLan: Пожалуйста, не печатайте. Скопируйте и вставьте из вашей локальной тестовой программы.   -  person Kerrek SB    schedule 22.02.2017
comment
@SananeLan Я так думаю, хотя я бы не сказал, что на 100% понимаю поведение .. UB также может быть по другим причинам :) Виртуальная диспетчеризация отключена в конструкторах и деструкторах   -  person M.M    schedule 22.02.2017


Ответы (3)


Комментарий @M.M попал в цель. Вы вызываете деструктор дважды. Это неопределенное поведение, и может произойти что угодно, включая поведение, которое вы наблюдаете.

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

Правильнее всего не вызывать деструктор вручную.

person Sebastian Redl    schedule 22.02.2017
comment
C++14 [class.dtor]/15 — это предложение, в котором конкретно говорится, что второй вызов деструктора — это UB для классов с нетривиальным деструктором (это так, поскольку virtual делает его нетривиальным) - person M.M; 22.02.2017

Виртуальные деструкторы в производном классе всегда будут сначала вызывать деструкторы родительского класса в рекурсивном порядке, так что будет вызываться самый «предковый» деструктор базового класса, а затем второй по величине «предковый» и т. д. Представьте, что Ребенок наследует от родителя, который наследуется от прародителя. Деструктор класса Child фактически вызовет деструктор GrandParent, затем деструктор Parent, затем деструктор Child.

Действительно, ваши конструкторы производных классов также вызывают конструкторы своих родительских классов в том же рекурсивном порядке. Вы должны представить производные классы как «слоеный пирог»: каждый экземпляр наследования добавляет слой к вашему объекту. Таким образом, класс Child имеет 3 слоя {GrandParent, Parent, Child}, построение/разрушение каждого слоя обрабатывается соответствующим классом.

То, что вы пытаетесь сделать, попытается дважды вызвать родительский деструктор, что является плохой идеей. Как правило, вам не нужно явно вызывать деструкторы, за исключением случая, когда вы перегрузили оператор new. См. этот ответ для получения более подробной информации: Всегда ли вызывает деструктор вручную признак плохого дизайна?

person Anton    schedule 22.02.2017

Вызов виртуального деструктора производного класса вызывает вызов деструктора базового класса. Но не наоборот.

person roman    schedule 22.02.2017