0

Работает ли оператор delete с указателями на базовый класс?

14

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

Описание проблемы: Я столкнулся с вопросом использования оператора delete в контексте наследования виртуальных базовых классов в C++. У меня есть следующий пример кода:

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; // Правильно ли это?

На первый взгляд, может показаться, что я могу использовать указатель pFoo, чтобы удалить объект Bar, потому что pFoo указывает на один из базовых типов. Однако мой внутренний голос подсказывает, что это неправильно, так как указатели не будут иметь одного и того же адреса.

С другой стороны, метод dynamic_cast<Bar*> должен работать, поэтому компилятор, похоже, хранит достаточную информацию для выполнения этого преобразования.

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

Вопрос: Может ли это работать? Как правильно управлять памятью в таких случаях, учитывая виртуальные деструкторы? И какова роль компилятора в этом процессе?

2 ответ(ов)

0

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

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

0

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

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

Пользовательские освобождающие ресурсы позволяют фабричной функции, возвращающей shared_ptr, изолировать пользователя от своей стратегии выделения памяти. Поскольку освобождающий ресурс не является частью типа, изменение стратегии выделения не нарушает совместимость исходного или двоичного кода и не требует повторной компиляции клиента. Например, "no-op" освобождающий ресурс полезен при возврате shared_ptr для статически выделенного объекта, а другие варианты позволяют использовать shared_ptr в качестве обертки для другого умного указателя, облегчая взаимодействие.

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

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь