Удаляет ли работа с указателями на базовый класс?
Вам нужно передать удаление того же указателя, который был возвращен новым, или вы можете передать ему указатель на один из базовых типов классов? Например:
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 и будет полагаться на подразумеваемый оператор delete в destructor shared_ptr, чтобы делать правильные вещи.
Может ли это работать? Моя интуиция говорит "нет", поскольку указатели не будут иметь одинаковый адрес. С другой стороны, dynamic_cast < Bar * > должен работать, поэтому где-то компилятор хранит достаточно информации, чтобы понять это.
Спасибо за помощь, всем, кто ответил и прокомментировал. Я уже знал важность виртуальных деструкторов, как показано в моем примере; увидев ответ, я немного подумал и понял, что
целая причина для виртуального деструктора - это точный сценарий. Таким образом, он должен был работать. Я был брошен из-за отсутствия видимых средств преобразования указателя обратно в оригинал. Немного больше мышления побудило меня поверить, что есть невидимые средства, и я предположил, что деструктор возвращает истинный указатель на удаление, чтобы освободить. Исследование скомпилированного кода из Microsoft VС++ подтвердило мое подозрение, когда я увидел эту строку в ~ Base:
mov eax, DWORD PTR _this$[ebp]
Отслеживание ассемблера показало, что это был указатель, передаваемый функции удаления. Тайна решена.
Я установил пример добавления виртуального деструктора в IFoo, это был простой надзор. Еще раз спасибо всем, кто указал на это.
Ответы
Ответ 1
Да, он будет работать, , если и только если деструктор базового класса является виртуальным, что вы сделали для базового класса Base
, но не для базового класса IFoo
. Если деструктор базового класса является виртуальным, то при вызове operator delete
в указателе базового класса он использует динамическую диспетчеризацию, чтобы выяснить, как удалить объект, просмотрев деструктор производного класса в таблице виртуальных функций.
В случае множественного наследования он будет работать только в том случае, если базовый класс, который вы удаляете, имеет виртуальный деструктор; это нормально, если другие базовые классы не имеют виртуального деструктора, но только если вы не пытаетесь удалить любые производные объекты с помощью других указателей базового класса.
Ответ 2
Это не относится к вашему приведенному примеру, но поскольку вы упомянули, что вы действительно заинтересованы в поведении shared_ptr
при удалении принадлежащего ему объекта, вам может быть интересно использовать shared_ptr
'deleter'.
Если объекту, принадлежащему shared_ptr
, требуется специальная обработка при удалении, вы можете указать "deleter" для любого конкретного shared_ptr<>
. Делетер не является частью этого типа, он является атрибутом экземпляра shared_ptr<>
, поэтому ваш контейнер объектов shared_ptr<>
может иметь некоторые объекты с разными удалениями. Здесь, что Boost docs говорят об shared_ptr<>
deleter:
Пользовательские деаллокаторы допускают factoryфункция возвращает shared_ptr
в изолировать пользователя от его памяти стратегии распределения. Поскольку deallocator не является частью типа, изменение стратегии распределения не прерывать источник или двоичный совместимости и не требует перекомпиляция клиента. Например, "de-op" deallocator полезен, когда возвращая a shared_ptr
к статически выделенный объект и другие варианты разрешите использовать shared_ptr
в качестве обертка для другого умного указателя, облегчая взаимодействие.
Было бы самым чистым, если бы вы могли изменить IFoo
, чтобы иметь виртуальный деструктор, поскольку вы планируете удалять объекты, которые являются его подклассами с помощью ссылки или указателя IFoo
. Но если вы застряли с IFoo
, который не может быть исправлен, то если вы хотите использовать shared_ptr<IFoo>
в своем контейнере, но если он указывает на Bar
, вы можете создать экземпляр shared_ptr
с удалением который выполняет downcast для Bar*
, а затем выполняет операцию удаления. Downcasts считаются плохой формой, но этот метод может быть тем, что вы могли бы использовать в привязке.