Перегрузка оператора delete в базовом классе
Из стандарта C++ (ISO/IEC 14882:2003(E)), §12.5.4, о перегрузке operator delete
:
Если выражение delete начинается с унарного оператора::, имя функции освобождения ищется в глобальной области видимости. В противном случае, если выражение delete используется для освобождения объекта класса, статический тип которого имеет виртуальный деструктор, функция освобождения является функцией, найденной поиском в определении виртуального деструктора динамического типа (12.4). В противном случае, если выражение delete используется для освобождения объекта класса T или его массива, статический и динамический типы объекта должны быть идентичными, а имя функции освобождения ищется в области видимости T. Если этот поиск не удается найти имя, имя ищется в глобальной области видимости. Если результат поиска является неоднозначным или недоступным, или если поиск выбирает функцию освобождения места размещения, программа некорректна.
§12.5.7 также интересен:
Поскольку функции распределения и освобождения членов являются статическими, они не могут быть виртуальными. [Примечание: однако, когда выражение приведения выражения delete относится к объекту типа класса, потому что фактически вызванная функция освобождения ищется в области видимости класса, который является динамическим типом объекта, если деструктор является виртуальным, эффект тот же. Например,
struct B {
virtual ˜B();
void operator delete(void*, size_t);
};
struct D : B {
void operator delete(void*);
};
void f()
{
B* bp = new D;
delete bp; // uses D::operator delete(void*)
}
Здесь хранилище для объекта, не являющегося массивом класса D, освобождается оператором D:: delete() из-за виртуального деструктора.]
Прочитав это, мне интересно...
- Эта часть стандарта полностью поддерживается всеми основными компиляторами C++ (MSVC++, GCC)?
- Если так, то как они это сделали? Скрытая виртуальная функция? "Специальный" виртуальный вызов деструктора? RTTI?
- Используя пример из стандарта: могут ли быть проблемы, если f() и оператор удаления D:: () определены в отдельных файлах EXE/DLL/DSO? (Конечно, при условии, что все скомпилировано с использованием одного и того же компилятора)
§5.3.5.5 также может иметь отношение к:
В первом альтернативе (удаление объекта), если статический тип операнда отличается от его динамического типа, статический тип должен быть базовым классом динамического типа операнда, а статический тип должен иметь виртуальный деструктор, или поведение не определено, Во втором варианте (удаление массива), если динамический тип удаляемого объекта отличается от его статического типа, поведение не определено.
2 ответа
Я не знаю много о VC++ ABI, но Itanium ABI хорошо документирован.
Глядя на схему искажения имени, можно увидеть:
<ctor-dtor-name> ::= C1 # complete object constructor
::= C2 # base object constructor
::= C3 # complete object allocating constructor
::= D0 # deleting destructor
::= D1 # complete object destructor
::= D2 # base object destructor
Представляет интерес: D0 # deleting destructor
Это означает, что даже если delete
не является виртуальным, так как он вызывается из виртуального деструктора, его можно считать виртуальным для всех эффектов и целей.
После копания в ассемблерном коде в GCC 4.8.
GCC сгенерирует две части кода (для класса, деструктор которого является виртуальным):
One is assembly snippet#1 for {Destructor + Dealloc}
The other is assembly snippet#2 for {Destructor only}
А для класса, деструктор которого не является виртуальным, инструкция функции освобождения вызова сгенерирует в точке, где вы вызываете delete.
(Следующее обсуждение предполагает, что деструктор является виртуальным). Так что для следующего кода:
delete C // This will be translate as call snippet#1 for the correct dynamic type
И если вы код следующий:
p->C::~C() // this will be translate to call snippet#2
Таким образом, функция deallocate связывается вместе с виртуальным деструктором. Так что я думаю, что это ответит на ваш вопрос о том, как функция deallocate реализована как виртуальная, так и статическая.