Подтвердить что ты не робот

С++ с использованием scoped_ptr в качестве переменной-члена

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

class Example {
public: 
  // ...

private:
  boost::scoped_ptr<Owned> data;
};

Объект "Owned" не может быть сохранен по значению, поскольку он может меняться в течение всего жизненного цикла объекта.

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

Followup: Просто хотел сказать спасибо за все ваши ответы. Спасибо за хедз-ап о auto_ptr, оставив другой объект с указателем NULL, когда весь объект скопирован, я использовал auto_ptr экстенсивно, но пока не думал об этом. Я делаю в основном все мои классы boost:: noncopyable, если у меня нет веской причины, так что там не о чем беспокоиться. И спасибо также за информацию об утечке памяти в исключениях, что тоже хорошо знать. Я стараюсь не писать вещи, которые могут вызвать исключения в конструкторе, в любом случае - есть лучшие способы сделать это - так что это не должно быть проблемой.

У меня был еще один вопрос. То, что я хотел, когда я задавал этот вопрос, состоял в том, чтобы знать, действительно ли кто-то это сделал, и все вы, кажется, упоминаете, что это хорошая идея теоретически, но никто не сказал, что они действительно это делают. Что меня удивляет! Конечно, один объект, владеющий указателем на другой, не является новой идеей, я бы ожидал, что вы все это сделаете раньше, в какой-то момент. Что происходит?

4b9b3361

Ответ 1

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

Вы должны помнить, что scoped_ptr является не подлежащим копированию, что делает ваш класс не подлежащим копированию по умолчанию до/если вы не добавите свой собственный конструктор копирования и т.д. (Конечно, использование конструктора копии по умолчанию в случае необработанных указателей будет нет-нет!)

Если ваш класс имеет более одного поля указателя, то использование scoped_ptr фактически улучшает безопасность исключений в одном случае:

class C
{
  Owned * o1;
  Owned * o2;

public:
  C() : o1(new Owned), o2(new Owned) {}
  ~C() { delete o1; delete o2;}
};

Теперь представьте, что во время построения C второй "новый Owned" выдает исключение (например, вне памяти). o1 будет просочиться, потому что C:: ~ C() (деструктор) не будет вызван, потому что объект еще не был полностью сконструирован. Хотя деструктор любого полностью построенного поля элемента называется. Таким образом, использование scoped_ptr вместо простого указателя позволит oj правильно уничтожить.

Ответ 2

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

  • Скопируемый: можно скопировать смарт-указатель: копия и исходная собственность.
  • Movable: умный указатель может быть перемещен: результат перехода будет иметь собственность, оригинал больше не будет принадлежать.

Это довольно распространенная терминология. Для интеллектуальных указателей существует определенная терминология, которая лучше маркирует эти свойства:

  • Передача собственности: умный указатель движется
  • Доля владения: умный указатель можно копировать. Если интеллектуальный указатель уже можно копировать, он легко поддерживает семантику передачи права собственности: это просто атомная копия и операция reset -of-original, ограничивающая это для интеллектуальных указателей определенных типов (например, только временные интеллектуальные указатели).

Сгруппируйте доступные интеллектуальные указатели, используя (C)opyable и (M)ovable, (N)either:

  • boost::scoped_ptr: N
  • std::auto_ptr: M
  • boost::shared_ptr: C

auto_ptr имеет одну большую проблему, поскольку реализует концепцию Movable с использованием конструктора копирования. Это связано с тем, что, когда auto_ptr был принят в С++, еще не было способа естественной поддержки семантики перемещения с использованием конструктора перемещения, в отличие от нового стандарта С++. То есть вы можете сделать следующее с auto_ptr, и он работает:

auto_ptr<int> a(new int), b;
// oops, after this, a is reset. But a copy was desired!
// it does the copy&reset-of-original, but it not restricted to only temporary
// auto_ptrs (so, not to ones that are returned from functions, for example).
b = a; 

В любом случае, как видим, в вашем случае вы не сможете передать право собственности другому объекту: ваш объект будет несовместимым. И в следующем стандарте С++ он будет не движимым, если вы останетесь с scoped_ptr.

Для реализации вашего класса с scoped_ptr, убедитесь, что у вас есть одна из этих двух точек:

  • Напишите деструктор (даже если он пуст) в .cpp файле вашего класса или
  • Make Owned a полностью определяет класс.

В противном случае, когда вы создадите объект примера, компилятор будет неявно определять деструктор для вас, который вызовет scoped_ptr destructor:

~Example() { ptr.~scoped_ptr<Owned>(); }

Затем будет выполнен вызов scoped_ptr boost::checked_delete, который будет жаловаться на то, что Owned будет неполным, если вы не выполнили ни одну из двух вышеуказанных пунктов. Если вы определили свой собственный dtor в файле .cpp, неявный вызов деструктора scoped_ptr будет сделан из файла .cpp, в котором вы можете поместить определение своего класса Owned.

У вас есть та же проблема с auto_ptr, но у вас есть еще одна проблема: предоставление auto_ptr с неполным типом - это поведение undefined в настоящее время (возможно, оно будет исправлено для следующей версии С++). Таким образом, когда вы используете auto_ptr, вы должны сделать Owned полным типом в своем заголовочном файле.

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

Ответ 3

Это не переполняет, это хорошая идея.

Это требует, чтобы ваши клиенты класса знали о повышении. Это может быть или не быть проблемой. Для переносимости вы можете рассмотреть std:: auto_ptr, который делает (в данном случае) ту же работу. Поскольку это личное, вам не нужно беспокоиться о том, что другие люди пытаются его скопировать.

Ответ 4

Скобочные указатели хороши именно для этого, потому что они гарантируют, что объекты будут удалены без необходимости беспокоиться об этом как программист. Я думаю, что это хорошее использование sctr poped.

Я считаю, что хорошая стратегия разработки в целом заключается в том, чтобы избежать как можно большего освобождения памяти вручную и позволить вашим инструментам (в данном случае, умным указателям) сделать это за вас. Удаление вручную плохо по одной основной причине, так как я вижу это, и это то, что код становится очень трудно поддерживать очень быстро. Логика выделения и освобождения памяти часто раздельна в коде, и это приводит к тому, что дополнительные линии не поддерживаются вместе.

Ответ 5

Я не думаю, что это слишком много, это семантика элемента намного лучше, чем наличие необработанного указателя и менее подверженная ошибкам.

Ответ 6

Использование scoped_ptr - хорошая идея.

Сохранение и ручное уничтожение указателя не так просто, как вы думаете. Особенно, если в вашем коде имеется более одного указателя RAW. Если безопасность исключений и не утечка памяти является приоритетом, тогда вам нужно много дополнительного кода, чтобы получить правильную информацию.

Для начала вам необходимо убедиться, что вы правильно определили все четыре метода по умолчанию. Это связано с тем, что сгенерированная компилятором версия этих методов подходит для обычных объектов (включая интеллектуальные указатели), но в нормальном случае это приведет к проблемам с обработкой указателя (см. "Проблема с мелкой копией" ).

  • Конструктор по умолчанию
  • Конструктор копирования
  • Оператор присваивания
  • Destructor

Если вы используете scoped_ptr, вам не нужно беспокоиться об этом.

Теперь, если у вас есть более одного указателя RAW в вашем классе (или другие части вашего конструктора могут бросать). Вы должны ОСУЩЕСТВЛЯТЬ с исключениями во время строительства и разрушения.

class MyClass
{
    public:
        MyClass();
        MyClass(MyClass const& copy);
        MyClass& operator=(MyClass const& copy);
        ~MyClass();

    private
        Data*    d1;
        Data*    d2;
};

MyClass::MyClass()
    :d1(NULL),d2(NULL)
{
    // This is the most trivial case I can think off
    // But even it looks ugly. Remember the destructor is NOT called
    // unless the constructor completes (without exceptions) but if an
    // exception is thrown then all fully constructed object will be
    // destroyed via there destructor. But pointers don't have destructors.
    try
    {
        d1 = new Data;
        d2 = new Data;
    }
    catch(...)
    {
        delete d1;
        delete d2;
        throw;
    }
}

Посмотрите, насколько проще scopted_ptr.

Ответ 7

Почему избыток? boost:: scoped_ptr очень легко оптимизировать, и я уверен, что полученный машинный код будет таким же, как если бы вы вручную удалили указатель в деструкторе.

scoped_ptr хорош - просто используйте его:)