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

Предположим, у меня есть следующий код (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 и deleteclass 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 не являются virtual, поэтому их присутствие в таблице vtable не обязательно. Я не думаю, что они имели бы большой смысл virtual, но у меня никогда не было причин пробовать это.

user4581301 24.06.2024 23:31

операторы new/delete являются статическими, даже если они не объявлены таковыми

Gene 24.06.2024 23:32

Четверть-половина вашего ответа здесь: Удаление виртуального оператора C++? При перечитывании думаю, что ссылка забивает примерно 3/4 ответа.

user4581301 24.06.2024 23:34

Чтобы этот вопрос и ответ(ы) дошли до людей, которым они будут полезны, я предлагаю пометить вопрос g++ или подобным. Подробности, которые вы описываете о vtable, относятся к (версиям) этого компилятора, и любые ответы также будут относиться к этому компилятору. В стандарте C++ нет требований к тому, чтобы существовала виртуальная таблица или чтобы другие реализации работали так же, как g++ (а другие реализации реализуют вещи иначе, чем g++).

Peter 25.06.2024 07:36

В C++ ничего не говорится о виртуальной таблице. То, на что вы смотрите, — это детали реализации.

463035818_is_not_an_ai 25.06.2024 09:21

@Питер, я проверил Clang - MSVC, и они следуют одному и тому же поведению, не уверен, реализуют ли они его таким же образом или нет.

Shady Atef 25.06.2024 22:57

Стандарт может не указывать виртуальную таблицу, но он определяет поведение виртуальных функций. Особенно это касается деструкторов.

Mark Ransom 25.06.2024 23:18

Ну да, стандарт определяет требуемое наблюдаемое поведение. В нем не указано, как достигается такое поведение. И детали, которые вы просите прокомментировать, связаны с тем, как один компилятор реализует требуемое поведение. Это возвращает нас к моему первоначальному предложению использовать тег g++, поскольку ваш вопрос специфичен для этого компилятора.

Peter 26.06.2024 03:26
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
16
8
1 381
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В 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 25.06.2024 09:20

@ 463035818_is_not_an_ai Я понял, что ОП спрашивает о реализации, а не о самом поведении. Но я добавил краткое объяснение в начале.

user17732522 25.06.2024 20:24

Другие вопросы по теме