чтобы лучше понять эту проблему, давайте посмотрим код1:
// code1
struct A {
int e;
};
struct B : A {
virtual ~B() { }
};
struct C : B {
~C() { }
};
как видите, класс A тривиально разрушаем
Q1: будет ли поведение неопределенным, если мы уничтожим экземпляр класса C через указатель класса A в code1?
Q2: поведение не определено, если мы уничтожаем экземпляр класса C через указатель класса B в code1?
вот другая версия, назовите ее code2:
//code2
struct A {
int e;
~A() { }
};
struct B : A {
virtual ~B() { }
};
struct C : B {
~C() { }
};
на данный момент класс A имеет предоставленный пользователем деструктор. так что класс A не является тривиально разрушаемым
Q3: будет ли поведение неопределенным, если мы уничтожим экземпляр класса C через указатель класса A в code2?
Q4: поведение не определено, если мы уничтожаем экземпляр класса C через указатель класса B в code2?
Я ознакомился со стандартом ISO/IEC 14882:2011(E) [expr.delete]/3
В первом варианте (удалить объект), если статический тип удаляемого объекта отличается от его динамического типа, статический тип должен быть базовым классом динамического типа удаляемого объекта, а статический тип должен иметь виртуальный деструктор или поведение не определено. Во втором варианте (удаление массива), если динамический тип удаляемого объекта отличается от его статического типа, поведение не определено.
Q5: стандарт говорит, что статический тип должен иметь виртуальный деструктор, но как насчет базового класса этого статического типа?
@JasonLiam все эти вопросы пытаются четко объяснить одну проблему, я не думаю, что эти вопросы можно задавать отдельно
Возможно, вам будет проще забыть о стандарте и подумать о том, что может сделать компилятор. Если компилятор знает определение A
, но не знает определения B
и C
, что он будет делать при удалении указателя на A
?
@JaMiT, поэтому, когда статический тип — A, а A не имеет виртуального деструктора, независимо от того, разрушаем он тривиально или нет, это UB? как насчет удаления указателя на B?
Отвечает ли это на ваш вопрос? Будет ли использование удаления с указателем базового класса вызывать утечку памяти?
Также по теме: Когда использовать виртуальные деструкторы? Это неопределенное поведение или нет? Множественное наследование и полиморфизм
@iTruth Изобретение неуместных осложнений не меняет того, что говорит стандарт. Не думайте, что он говорит больше или меньше, чем на самом деле. Как вы процитировали, «статический тип должен иметь виртуальный деструктор, иначе поведение не определено». Где упоминается что-то, связанное с тривиально разрушаемым? Где это говорит о том, что базовые классы статического класса имеют значение? Найдите статический класс, посмотрите на его деструктор, и вы знаете все, что вам нужно знать.
@JaMiT спасибо за ваш ответ, я прочитал весь ваш пост. к сожалению, все эти сообщения не отвечают на мой вопрос. Я думаю, что моя проблема немного в другом. как вы знаете, класс A в code1 является классом POD, так что я могу передать его непосредственно в код C. но могу ли я получить класс от A, который я могу использовать ООП?
Я не понимаю, что в ответе JaMiT не отвечает на ваш вопрос или что делает вашу ситуацию «немного другой». ПОД не имеет значения. Удаление C
через указатель на A
, когда A
не имеет виртуального деструктора, является поведением undefined. Если этот факт мешает чему-то другому, что вы пытаетесь сделать, вам может быть лучше задать вопрос, который действительно касается того, что вы пытаетесь сделать.
@NathanPierson, а не ответ JaMiT, не отвечает на мой вопрос, я имею в виду ссылку, которую он публикует. его ответ имеет высокую референсную ценность. на данный момент я знаю, что удаление C через указатель на A, когда A не имеет виртуального деструктора, является неопределенным поведением. а как насчет удаления C через указатель на B?
Если у типа есть виртуальный деструктор, компилятор вызовет правильный деструктор для указателя этого типа, даже если экземпляр действительно принадлежит производному классу. (На практике он выберет указатель функции деструктора из VMT.) Если у него нет виртуального деструктора, он уничтожит тип указателя. (Нет записи в VMT.) Итак: Q1: Да Q2: Нет Q3: Да Q4: Нет Q5: Если какой-либо базовый класс имеет виртуальный деструктор: Нет, в противном случае Да.
К вашему сведению: демо на coliru
В вашем примере:
A* a = new C();
delete a; // UB
статический тип A
. Неважно, что B
делает свой деструктор виртуальным. Здесь рассматривается только один деструктор A::~A()
(в обоих вариантах вашего кода). Теперь, если у вас есть это:
B* b = new C();
delete b; // not UB
статический тип b
не A
, а B
. B
однако имеет виртуальный деструктор и поэтому сначала уничтожит C
, затем B
и, наконец, свою базу A
. Это ничем не отличается от иерархии классов без виртуальных деструкторов, когда вы уничтожаете самый производный тип:
struct X {};
struct Y : X {};
struct Z : Y {};
Z* z = new Z();
delete z; // destroys Z, then Y, then X (not UB)
для того, чтобы это работало, нет большой разницы между тривиальными и нетривиальными деструкторами.
Чтобы ответить на ваш последний вопрос: статический тип, который вы хотите уничтожить, должен иметь виртуальный деструктор или быть идентичным динамическому типу. В противном случае все, что выше статического типа, будет ошибочно пропущено. Но типы ниже статического типа статически известны как базовые классы статического типа, поэтому виртуальный деструктор не нужен.
Один вопрос на пост. В вашем посте 5 вопросов.