Я работаю над старой базой кода С++ 03. Один раздел выглядит примерно так:
#include <cstddef>
struct Pool
{ char buf[256]; };
struct A
{ virtual ~A() { } };
struct B : A
{
static void *operator new(std::size_t s, Pool &p) { return &p.buf[0]; }
static void operator delete(void *m, Pool &p) { } // Line D1
static void operator delete(void *m) { delete m; } // Line D2
};
Pool p;
B *doit() { return new(p) B; }
То есть, B происходит от A, но экземпляры B выделяются из пула памяти.
(Обратите внимание, что этот пример немного упрощен... На самом деле распределитель пулов делает что-то нетривиальным, поэтому требуется размещение operator delete
в строке D1.)
В последнее время мы включили больше предупреждений о компиляторах, а в строке D2 появляется следующее предупреждение:
предупреждение: удаление 'void * - undefined [-Wdelete-incomplete]
Хорошо, да, очевидно. Но поскольку эти объекты всегда выделены из пула, я решил, что нет необходимости в настраиваемом (не размещении) operator delete
. Поэтому я попытался удалить Line D2. Но это привело к сбою компиляции:
new.cc: В destructor 'virtual B:: ~ B(): new.cc:9:8: ошибка: нет подходящий 'оператор delete для' B struct B: A ^ new.cc: В глобальной области: new.cc:18:31: note: синтезированный метод 'virtual B:: ~ B() сначала требуется здесь B * doit1() {return новый (p) B; }
Небольшое исследование определило, что проблема - виртуальный деструктор B. Он должен вызывать не размещение B::operator delete
, потому что кто-то может попробовать delete
a B
через A *
. Благодаря скрытию имени строка D1 делает недоступным по умолчанию размещение operator delete
недоступным.
Мой вопрос: как лучше всего справиться с этим? Одно очевидное решение:
static void operator delete(void *m) { std::terminate(); } // Line D2
Но это кажется неправильным... Я имею в виду, кем я должен настаивать на том, что вы должны выделить эти вещи из пула?
Еще одно очевидное решение (и то, что я сейчас использую):
static void operator delete(void *m) { ::operator delete(m); } // Line D2
Но это также кажется неправильным, потому что, как я знаю, я вызываю функцию правильного удаления?
То, что я действительно хочу, я думаю, это using A::operator delete;
, но это не скомпилируется ( "никакие члены не сопоставляют" оператор A:: "delete в" struct A ").
Связанные, но разные вопросы:
Почему для виртуальных деструкторов требуется оператор удаления
[Обновить, чтобы развернуть бит]
Я забыл упомянуть, что деструктор для A
не обязательно должен быть virtual
в нашем текущем приложении. Но вывод из класса с не виртуальным деструктором заставляет некоторых компиляторов жаловаться, когда вы поднимаете уровень предупреждения, а исходной точкой упражнения было устранение таких предупреждений.
Также, чтобы быть понятным по желаемому поведению... Нормальный пример использования выглядит так:
Pool p;
B *b = new (p) B;
...
b->~B();
// worry about the pool later
То есть, как и большинство применений размещения new, вы вызываете деструктор напрямую. Или вызовите вспомогательную функцию, чтобы сделать это для вас.
Я не ожидал, что следующее будет работать; на самом деле, я бы счел это ошибкой:
Pool p;
A *b_upcast = new (p) B;
delete b_upcast;
Обнаружение и отказ при таком ошибочном использовании будет прекрасным, но только если это можно сделать без добавления каких-либо накладных расходов в не ошибочные случаи. (Я подозреваю, что это невозможно.)
Наконец, я ожидаю, что это сработает:
A *b_upcast = new B;
delete b_upcast;
Другими словами, я хочу поддерживать, но не требует использования распределителя пулов для этих объектов.
Мое текущее решение работает в основном, но я обеспокоен тем, что прямой вызов ::operator delete
не всегда прав.
Если вы думаете, что у вас есть хороший аргумент, что мои ожидания в отношении того, что должно или не должно работать, являются неправильными, я тоже хотел бы это услышать.