Почему оператор удаления должен быть статическим?

Я нашел этот вопрос, задающий тот же вещь, однако был дан ответ только на «новую» часть, так что снова.

Почему оператор удаления должен быть статическим? Как-то не имеет смысла. Новый оператор имеет смысл, точно так же, как конструктор не может быть виртуальным, так же как и новый оператор. Однако деструктор может (и должен) быть виртуальным, когда вы используете наследование, чтобы разрешить уничтожение объектов, используемых (посредством полиморфизма) в качестве базового класса.

Я понимаю, что когда вызывается оператор удаления, объект уже уничтожен, поэтому «этого» не существует. Тем не менее, по тем же соображениям, что и в случае с виртуальным деструктором, по-прежнему имеет смысл, чтобы оператор удаления соответствовал оператору нового, создавшему объект.

Это то, что я имею в виду

class A
{
  public:
    virtual ~A() {}
};

class B : public A
{
  public:
    void* operator new (size_t sz);
    void  operator delete (void* ptr, size_t sz);
};

теперь, если мы сделаем

A *ptr = new B();
delete ptr; // <-- fail

Оператор удаления A (по умолчанию) должен был быть вызван, поскольку он статичен и во время компиляции неизвестно (ни для чего, кроме тривиального случая), какой оператор удаления является правильным.

Тем не менее, я сделал небольшую тестовую программу с приведенным выше кодом (просто malloc/free в операторах new/delete и оператор печати в delete) и скомпилировал ее с помощью g++. Его запуск совершенно неожиданно привел к выводу в операторе удаления B.

Мой (настоящий) вопрос заключается в следующем: есть ли какая-то неявная «виртуальность» в операторе удаления? Является ли он статическим только в смысле отсутствия этого указателя? Или это просто функция g++?

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


person falstro    schedule 16.02.2010    source источник


Ответы (2)


Ответ в правилах языка действительно находится в 12.5 [class.free].

Если вы удаляете через указатель на базовый класс, то деструктор должен быть виртуальным, иначе вы получите неопределенное поведение. В противном случае реализация должна определить динамический тип удаляемого объекта.

12.5/4 говорит, что когда delete не имеет префикса ::, тогда функция освобождения определяется путем поиска delete в контексте виртуального деструктора динамического типа. Это обеспечивает виртуальный поиск, хотя operator delete всегда является static функцией-членом.

Необработанное выделение и освобождение концептуально происходят за пределами жизненного цикла объекта, поэтому к моменту вызова функции освобождения уже нет объекта, обеспечивающего механизм виртуального поиска, но правила поиска гарантируют, что operator delete имеет динамический (виртуальный-облегченный! ) механизм поиска. Это означает, что оператор удаления вполне может быть static без потери связи с динамическим типом исходного объекта.

person CB Bailey    schedule 16.02.2010
comment
В 5.3.5, а также в 3.7.3 и 12.5 также есть соответствующие вещи. Несмотря на то, что он заявлен как справочная работа, кажется, что вы должны прочитать его от корки до корки, иначе вы никогда не узнаете, есть ли небольшой абзац, относящийся к чему-то, что вы ищете, в совершенно другом разделе, чем тот, где вы были на самом деле. Ищу. - person CB Bailey; 16.02.2010

Оператор delete предназначен только для освобождения памяти, а память освобождается для объекта самого производного класса в целом - за одно действие - точно так же, как с оператором new она выделяется для всего объекта самого производного класса - объекта класса передается как аргумент в конструкцию new Class.

Вот почему, когда вы делаете delete ptr;, оператор delete всегда вызывается только один раз для фактического наиболее производного класса удаляемого объекта, а данные о том, какой это класс, выводятся либо из виртуальной таблицы, если присутствует виртуальный деструктор, либо из типа указателя, если нет виртуального деструктора. Вот почему в операторе delete нет неявной виртуальности — вся виртуальность заканчивается в точке вызова деструктора.

person sharptooth    schedule 16.02.2010
comment
@roe: оператор удаления самого производного класса вызывается так же, как вызывается самый производный оператор нового класса. Таким образом, если у вас есть разные банки, вы получите объекты разных классов, выделенные из разных банков, но только как целые объекты, никогда не случится так, что подобъект будет из одного банка, а часть производного класса объекта из другого. - person sharptooth; 16.02.2010
comment
Таким образом, используемый оператор выводится из того, к какому классу относится фактический объект (т. е. он ищет виртуальную таблицу или, как бы то ни было, он работает), звучит так, будто в операторе есть какая-то подразумеваемая виртуальность? Предполагая, что у меня есть класс C, производный от B, A* ptr = new C; delete ptr; по-прежнему вызывает оператор удаления B, что мне кажется в значительной степени виртуальным. Извините за спам в комментариях, я просто пытаюсь понять это. - person falstro; 16.02.2010
comment
@roe: операторы удаления и новые наследуются. Поскольку вы перегрузили их в классе B, класс C также будет их использовать. Посмотрите красивую диаграмму здесь: msdn.microsoft.com/en-us/library/ c5at8eya.aspx - person sharptooth; 16.02.2010
comment
Я думаю, что я пытаюсь сказать, что сам объект должен нести информацию о том, какой оператор удаления использовать для этого объекта, то есть оператор удаления неявно виртуален. Вы случайно не знаете, где я могу найти это в спецификации? - person falstro; 16.02.2010
comment
@roe: Нет, я не знаю, где это в спецификации. Я думаю, что ключевым моментом здесь является использование виртуальных деструкторов - если они у вас есть, все остальное работает, в противном случае это зависит. - person sharptooth; 16.02.2010