Здравствуйте, fk0, Вы писали:
fk0> Когда ты пишешь delete xxx, то компилятор это превращает в xxx->~XXX(), ::operator delete(xxx).
Это всё же не совсем так:
delete xxx — это не синтаксический сахар для
xxx->~XXX(); ::operator delete(xxx);.
И если заменить первое на второе, то программы
начнут падать.
На самом же деле это только в С++ коде у класса один деструктор, а в скомпилированном коде у класса много деструкторов: например
deleting destructor,
complete object destructor,
base object destructor (в терминологии
itanium c++ abi).
И когда ты пишешь
delete xxx, то вызывается
deleting destructor, а когда пишешь
xxx->~XXX(), то вызывается
complete object destructor.
Это разные деструкторы именно из-за того, что один нельзя тривиально реализовать через другой, в том числе
deleting destructor нельзя выразить как вызов
complete object destructor за которым следует вызов
::operator delete(xxx) для того же самого указателя. Пример с виртуальным базовым классом это как раз показывает. И поэтому
deleting destructor сначала определяет где была выделена память (для чего нужен неразрушенный объект), потом разрушает поля и базовые классы, и освобождает память для другого указателя, найденного на первом шаге.
P.S. В других ABI (не itanium c++ abi), реализация может чуть отличаться. Например, MSVC генерирует в машинном коде одну функцию, реализующую оба поведения. Но зато эта функция принимает в себя дополнительный параметр (помимо
this), говорящий в каком режиме деструктор был вызван, и содержит ветвление уже внутри своего тела. Но главное, что там тоже происходят коррекции адресов.
Здравствуйте, watchmaker, Вы писали:
W>Здравствуйте, fk0, Вы писали:
fk0>> Когда ты пишешь delete xxx, то компилятор это превращает в xxx->~XXX(), ::operator delete(xxx).
W>Это всё же не совсем так: delete xxx — это не синтаксический сахар для xxx->~XXX(); ::operator delete(xxx);.
W>И если заменить первое на второе, то программы начнут падать.
W>На самом же деле это только в С++ коде у класса один деструктор, а в скомпилированном коде у класса много деструкторов: например deleting destructor, complete object destructor, base object destructor (в терминологии itanium c++ abi).
W>И когда ты пишешь delete xxx, то вызывается deleting destructor, а когда пишешь xxx->~XXX(), то вызывается complete object destructor.
W>Это разные деструкторы именно из-за того, что один нельзя тривиально реализовать через другой, в том числе deleting destructor нельзя выразить как вызов complete object destructor за которым следует вызов ::operator delete(xxx) для того же самого указателя. Пример с виртуальным базовым классом это как раз показывает. И поэтому deleting destructor сначала определяет где была выделена память (для чего нужен неразрушенный объект), потом разрушает поля и базовые классы, и освобождает память для другого указателя, найденного на первом шаге.
Ценное замечание. Можно ли его обобщить, сказав, что в общем случае мы не знаем адрес в памяти, который возвращал
::operator new() когда мы делали new class() ? И, следовательно, вручную вызывать ::operator delete() можно только
если этот адрес нам известен, что могло бы только если ::operator new() вызывался вручную и при этом мы сознательно
отличаем адрес возвращённый ::operator new() и адрес полученный вследствие вызова placement new (с адресом полученным
от ::operator new()), и понимаем, что эти два адреса всегда различны и для последующего вызова деструктора и
::operator delete() мы вынуждены хранить оба адреса. И, следовательно, placement new может в качестве результата
вернуть адрес который отличается от адреса переданного ему в аргументах. Но по чертовской случайности эти оба адреса
часто совпадают, благодаря чему на грабли можно наступить далеко не сразу.