Работает ли delete с указателями на базовый класс? - PullRequest
44 голосов
/ 17 ноября 2008

Вам нужно передать delete тот же указатель, который был возвращен новым, или вы можете передать ему указатель на один из базовых типов классов? Например:

class Base
{
public:
    virtual ~Base();
    ...
};

class IFoo
{
public:
    virtual ~IFoo() {}
    virtual void DoSomething() = 0;
};

class Bar : public Base, public IFoo
{
public:
    virtual ~Bar();
    void DoSomething();
    ...
};

Bar * pBar = new Bar;
IFoo * pFoo = pBar;
delete pFoo;

Конечно, это значительно упрощается. Что я действительно хочу сделать, так это создать контейнер, полный boost :: shared_ptr, и передать его некоторому коду, который удалит его из контейнера после его завершения. Этот код ничего не будет знать о реализации Bar или Base и будет полагаться на подразумеваемый оператор удаления в деструкторе shared_ptr, чтобы делать правильные вещи.

Может ли это сработать? Моя интуиция говорит «нет», поскольку указатели не будут иметь один и тот же адрес. С другой стороны, dynamic_cast должен работать, поэтому где-то компилятор хранит достаточно информации, чтобы понять это.


Спасибо за помощь всем, кто ответил и прокомментировал. Я уже знал важность виртуальных деструкторов, как показано в моем примере; Увидев ответ, я немного подумал и понял, вся причина для виртуального деструктора - именно этот сценарий. Таким образом, это должно было работать. Меня бросило отсутствие видимого средства преобразования указателя обратно в оригинал. Немного больше размышлений заставило меня поверить, что существуют невидимые средства, и я предположил, что деструктор возвращает истинный указатель для удаления, чтобы освободить. Исследование скомпилированного кода из Microsoft VC ++ подтвердило мои подозрения, когда я увидел эту строку в ~ Base:
mov eax, DWORD PTR _this$[ebp]

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

Я исправил пример добавления виртуального деструктора в IFoo, это был простой недосмотр. Еще раз спасибо всем, кто указал на это.

Ответы [ 2 ]

56 голосов
/ 17 ноября 2008

Да, это будет работать, тогда и только тогда, когда деструктор базового класса является виртуальным, что вы сделали для базового класса Base, но не для базового класса IFoo. Если деструктор базового класса является виртуальным, то при вызове operator delete для указателя базового класса он использует динамическую диспетчеризацию, чтобы выяснить, как удалить объект путем поиска деструктора производного класса в таблице виртуальных функций.

В случае множественного наследования оно будет работать, только если базовый класс, через который вы его удаляете, имеет виртуальный деструктор; для других базовых классов нормально иметь виртуальный деструктор, но только если вы не пытаетесь удалить какие-либо производные объекты с помощью этих других указателей базового класса.

3 голосов
/ 17 ноября 2008

Это не относится к вашему данному примеру, но, поскольку вы упомянули, что вы действительно заинтересованы в поведении shared_ptr при удалении принадлежащего ему объекта, вас может заинтересовать использование shared_ptr 'deleteter' .

Если объект, принадлежащий shared_ptr, требует особой обработки при удалении, вы можете указать «deleteer» для любого конкретного shared_ptr<>. Средство удаления не является частью типа, оно является атрибутом экземпляра shared_ptr<>, поэтому в вашем контейнере объектов shared_ptr<> могут быть объекты с разными удалителями. Вот что говорят документы Boost об удалителе shared_ptr<>:

Таможенные деллокаторы позволяют фабрике функция, возвращающая shared_ptr изолировать пользователя от его памяти стратегия распределения. Поскольку Deloclocator не является частью типа, изменение стратегии распределения делает не разбивать исходный или двоичный совместимость и не требует перекомпиляция клиента. Например, «неоперативный» деллокатор полезен, когда возвращая shared_ptr статически выделенный объект и другие варианты позволяют использовать shared_ptr в качестве обертка для другого умного указателя, облегчение взаимодействия.

Было бы лучше, если бы вы могли изменить IFoo, чтобы иметь виртуальный деструктор, так как вы планируете удалять объекты, которые являются его подклассами, через ссылку или указатель IFoo. Но если вы застряли с IFoo, который не может быть исправлен, то, если вы хотите использовать shared_ptr<IFoo> в своем контейнере, но указав на Bar, вы можете создать экземпляр shared_ptr с удалителем который выполняет понижение до Bar*, затем выполняет операцию удаления. Даункасты считаются плохим тоном, но вы можете использовать эту технику в привязке.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...