Рекомендации по удалению ресурса в иерархии наследования. - PullRequest
8 голосов
/ 30 августа 2011

Рассмотрим следующий код:

class Base {
protected: 
    int* ptr_;

public:
    virtual ~Base() { /* delete ptr_ ?? */ }    
}

class A : public Base {
public:
    A() { ptr_ = new int; }
    ~A() { /* delete ptr_ ?? */ }
}

У меня небольшой спор с моим коллегой.
Какой деструктор следует удалить ptr_?
Я думаю, что это должно быть в A, потому что это где он расположен.
Он думает, что это должно быть в Base, потому что именно там находится указатель.

Пожалуйста, укажите возможные подводные камни, почему один метод лучше, чем другой (если он есть, и это не просто вопрос вкуса)

EDIT
Многие люди ставят под сомнение дизайн. На практике код намного сложнее и моделирует систему обработки изображений, включающую несколько камер. Роль Base заключается в управлении ресурсами системы (существует несколько конфигураций). Класс A и другие производные типы, подобные ему, определяют конфигурацию Base и инициализируют систему для конкретной конфигурации. Думаю, теперь вы можете спросить, является ли Inheritance правильным выбором по сравнению с Composition. A - это Base, просто особый вид, и именно поэтому мы выбрали наследование. В этом случае ptr_ является указателем на конкретный драйвер камеры, который будет определяться производным типом, поэтому он размещается там.

Ответы [ 9 ]

7 голосов
/ 30 августа 2011

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

Если некоторому производному классу разрешено не присваивать указателю новое значение, то установите его равным нулю в конструкторе базового класса. Если какой-либо производный класс должен иметь возможность назначать указателю адрес автоматической переменной, просмотрите ваш проект.

Используйте умные указатели, чтобы проблема полностью исчезла (когда я grep delete * во всей моей кодовой базе, я не нашел соответствия:)

6 голосов
/ 30 августа 2011

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

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

Если база не должна управлять ресурсом, то почему указатель на этом уровне? Почему он не является членом производного типа?

Обратите внимание, что при управлении ресурсом следует учитывать правило из трех 1 : если вам нужно реализовать одно из copy- конструктор , оператор присваивания или деструктор , тогда вы, вероятно, захотите реализовать три из них (дополнительно рассмотрите возможность реализации no-throw swap, что будет удобно).

Как только вы добавите copy-constructor и assignment-operator к миксу, вы, вероятно, увидите, что естественное место для управления ресурсом там, где он содержится.

1 Правило является общим, поскольку есть веские причины не всегда применять их все. Например, если вы управляете своими ресурсами внутри членов, которые реализуют RAII (как интеллектуальные указатели), вам может потребоваться предоставить copy-construction и оператора присваивания для семантики глубокого копирования , но уничтожение не потребуется. Кроме того, в некоторых случаях вам может потребоваться отключить * copy-construction * и / или назначений .

4 голосов
/ 30 августа 2011

На самом деле ptr_ должно быть private!(См. Скотт Мейерс Эффективный C ++ или любой учебник по ориентации объекта.)

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

A() : Base(new int()) { }
2 голосов
/ 30 августа 2011

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

Кроме того, время жизни указателя заканчивается в базовом классе, так что должно быть место для его освобождения. Фактически, я бы выделил указатель внутри базового класса, а не из производного класса.

Лучше всего использовать RAII , и не делить указатель явно, пусть Умные указатели сами позаботятся об освобождении.

1 голос
/ 30 августа 2011

Вы беспокоитесь о дублировании кода, но, боюсь, это омрачило ваше мнение.

Я знаю, что У СУХОГО много прессы (и на то есть веские причины), но вы неправильно это поняли. Есть разница между не дублированием кода и не дублированием функциональности .

DRY означает, что функциональность не дублируется. Могут быть шаблоны кода, которые похожи друг на друга, но используются для разных целей, их не следует объединять, поскольку каждая цель может смещаться независимо (а не объединение - это кошмар). Их сходство совпадение .

Это тот случай, здесь. Вы произвольно устанавливаете int* ptr_ в классе Base (который его не использует), потому что он наверняка понадобится всем производным классам. Откуда ты это знаешь ? На данный момент оказывается, что все производные классы нуждаются в этом, но это совпадение. Они могли бы полагаться на что-то другое.

Следовательно, вменяемый проект предусматривает предоставление интерфейса в базовом классе и предоставление каждому производному классу права выбора собственной реализации:

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

class D1: public Base {
public:
  D1(Foo const& f): ptr_(new Foo(f)) {}

private:
  std::unique_ptr<Foo> ptr_;
};

class D2: public Base {
public:
  D2(Bar const& f): ptr_(new Bar(f)) {}

private:
  std::unique_ptr<Bar> ptr_;
};

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

1 голос
/ 30 августа 2011

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

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

0 голосов
/ 30 августа 2011

Я бы сделал это, используя RAII .

class RaiiResource
{
private:
   int* ptr_;

public:
   RaiiResource()       {       ptr_ = new int;       }
   ~RaiiResource()       {      delete ptr_;       } 
    int* getResource()      {           return ptr_;        }
}

class Base 
{
protected:
    RaiiResource m_raiiresource;

    void anyMethod()
    {
        int* pIntNeeded = m_raiiresource.getResource();/*when you need the resource*/
    }

public:
    virtual ~Base() {}    
}

class A : public Base {
public:
    A() {}
    ~A() { }
    void anyOtherMethod()
    {
        int* pIntNeededAgain = m_raiiresource.getResource(); /*when you need the resource again somewhereelse*/
    }   
}

Другим способом было бы использовать std :: shared_ptr, который позволяет управлять областью жизни объектов между различными объектами, как в вашем случае (Base и A разделяют необходимость существования объекта (int)) *

0 голосов
/ 30 августа 2011

Для меня лучше всего иметь один и тот же объект, отвечающий за распределение и нераспределение.Так что в вашем коде class A будет отвечать за delete, поскольку он выполняет new.

Но: почему A выделил ptr_ вместо Base?Это настоящий вопрос здесь.Для меня проблема дизайна, поскольку ptr_ принадлежит Base.Либо он должен принадлежать A, либо Base должен отвечать за управление памятью.

0 голосов
/ 30 августа 2011

Лучше всего, если объект, создающий ресурс, должен удалить ресурс - это их ответственность.

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