Предположим, у меня есть следующий код (https://godbolt.org/z/MW4ETf7a8):
Х.ч.
#include <iostream>
struct X{
void* operator new(std::size_t size)
{
std::cout << "new X\n";
return malloc(size);
}
void operator delete(void* ptr)
{
std::cout << "delete X\n";
return free(ptr);
}
virtual ~X() = default;
};
struct ArenaAllocatedX : public X {
void* operator new(std::size_t size)
{
std::cout << "new ArenaAllocatedX\n";
return malloc(size);
}
void operator delete(void* ptr)
{
std::cout << "delete ArenaAllocatedX\n";
return free(ptr);
}
};
main.cpp
int main() {
X* x1 = new X();
delete x1;
X* x2 = new ArenaAllocatedX ();
delete x2;
}
Используя GCC 10.3.1,
x1
вызывает операторы new
и delete
class X
, а x2
вызывает class ArenaAllocatedX
.
Я понимаю, как он будет использовать оператор new
, поскольку справа от него указано фактическое имя класса, но я не понимаю, как оператор delete
выбирается для подкласса, когда на него указывает X*
.
Считаются ли операторы new
/delete
функциями внутри виртуальной таблицы (я сбросил VTable с помощью gcc -f-dump-lang-class
, но не вижу операторов удаления)?
Дамп VTable:
Vtable for X
X::_ZTV1X: 4 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI1X)
16 (int (*)(...))X::~X
24 (int (*)(...))X::~X
Class X
size=8 align=8
base size=8 base align=8
X (0x0x7f38cd807780) 0 nearly-empty
vptr=((& X::_ZTV1X) + 16)
Vtable for ArenaAllocatedX
ArenaAllocatedX::_ZTV15ArenaAllocatedX: 4 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI15ArenaAllocatedX)
16 (int (*)(...))ArenaAllocatedX::~ArenaAllocatedX
24 (int (*)(...))ArenaAllocatedX::~ArenaAllocatedX
Class ArenaAllocatedX
size=8 align=8
base size=8 base align=8
ArenaAllocatedX (0x0x7f38cd848680) 0 nearly-empty
vptr=((& ArenaAllocatedX::_ZTV15ArenaAllocatedX) + 16)
X (0x0x7f38cd807cc0) 0 nearly-empty
primary-for ArenaAllocatedX (0x0x7f38cd848680)
Как C++ выбирает, какой оператор delete
использовать в предоставленном коде?
операторы new/delete являются статическими, даже если они не объявлены таковыми
Четверть-половина вашего ответа здесь: Удаление виртуального оператора C++? При перечитывании думаю, что ссылка забивает примерно 3/4 ответа.
Чтобы этот вопрос и ответ(ы) дошли до людей, которым они будут полезны, я предлагаю пометить вопрос g++
или подобным. Подробности, которые вы описываете о vtable, относятся к (версиям) этого компилятора, и любые ответы также будут относиться к этому компилятору. В стандарте C++ нет требований к тому, чтобы существовала виртуальная таблица или чтобы другие реализации работали так же, как g++ (а другие реализации реализуют вещи иначе, чем g++).
В C++ ничего не говорится о виртуальной таблице. То, на что вы смотрите, — это детали реализации.
@Питер, я проверил Clang - MSVC, и они следуют одному и тому же поведению, не уверен, реализуют ли они его таким же образом или нет.
Стандарт может не указывать виртуальную таблицу, но он определяет поведение виртуальных функций. Особенно это касается деструкторов.
Ну да, стандарт определяет требуемое наблюдаемое поведение. В нем не указано, как достигается такое поведение. И детали, которые вы просите прокомментировать, связаны с тем, как один компилятор реализует требуемое поведение. Это возвращает нас к моему первоначальному предложению использовать тег g++
, поскольку ваш вопрос специфичен для этого компилятора.
В C++ указано, что в выражении с одним объектом (а не массивом) delete
, если предоставленный указатель указывает на тип с виртуальным деструктором, operator delete
будет вызываться для наиболее производного объекта объекта, на который указывает указатель. Поиск operator delete
также выполняется в области класса этого наиболее производного типа, а не в области класса статического типа операнда.
Это верно только в том случае, если операнд статического типа delete
является указателем на тип с виртуальным деструктором. В противном случае передача указателя подобъекта базового класса на delete
будет иметь неопределенное поведение. Распространенной ошибкой является забвение добавить виртуальный деструктор для типа, который используется полиморфно в базовом классе.
Итак, это формально объясняет наблюдаемое вами поведение. Что касается того, как компилятор достигает такого поведения:
Как вы можете видеть в своей виртуальной таблице, для каждого класса есть два деструктора.
Если у вас есть класс с виртуальным деструктором, то компилятор выдаст два экземпляра деструктора.
Одним из них является обычный деструктор, который выполняет те же операции, что и невиртуальный деструктор, и который будет использоваться, если вы явно вызовете деструктор.
Второй — так называемый удаляющий деструктор. Он также выполняет обычные действия деструктора (или вызывает первую реализацию), но дополнительно в конце вызывает operator delete
класса, к которому принадлежит деструктор.
Сам деструктор является виртуальным, и обе реализации деструктора можно найти для наиболее производного объекта с помощью виртуального поиска. Для выражения delete
компилятор затем вызовет удаляющий деструктор посредством виртуальной отправки. Поскольку вызов operator delete
встроен в этот деструктор, компилятору не нужно будет добавлять дополнительный вызов operator delete
на место выражения delete
.
«Обычный» деструктор тогда также называется деструктором базового объекта, поскольку он будет использоваться в случае подобъектов базового класса (если таковые имеются), которые сами по себе не имеют связанного выделения.
Из-за этой специальной реализации правила поиска operator delete
для виртуальных деструкторов также отличаются, а правила создания экземпляров шаблонов отличаются. operator delete
будет искаться в точке определения виртуального деструктора, а не в той точке, где появляется выражение delete
. Это также может привести к запутанным результатам.
возможно, стоит упомянуть, что это отвечает на вопрос: «Как g++ выбирает, какой оператор удаления использовать в предоставленном коде?» а не «Как работает C++...». Хотя это просто из-за вопроса, предполагающего, что то, что они видят в своей реализации, соответствует требованиям C++.
@ 463035818_is_not_an_ai Я понял, что ОП спрашивает о реализации, а не о самом поведении. Но я добавил краткое объяснение в начале.
Операторы
new
иdelete
не являютсяvirtual
, поэтому их присутствие в таблице vtable не обязательно. Я не думаю, что они имели бы большой смыслvirtual
, но у меня никогда не было причин пробовать это.