std :: auto_ptr или boost :: shared_ptr для идиомы pImpl? - PullRequest
16 голосов
/ 22 ноября 2008

При использовании идиомы pImpl предпочтительнее ли использовать boost:shared_ptr вместо std::auto_ptr? Я уверен, что однажды прочитал, что буст-версия более дружественна к исключениям?

class Foo
{
public:
    Foo();
private:
    struct impl;
    std::auto_ptr<impl> impl_;
};

class Foo
{
public:
    Foo();
private:
    struct impl;
    boost::shared_ptr<impl> impl_;
};

[EDIT] Всегда ли безопасно использовать std :: auto_ptr <> или существуют ситуации, когда требуется альтернативный интеллектуальный указатель для повышения?

Ответы [ 9 ]

38 голосов
/ 22 ноября 2008

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

Если вы используете boost :: scoped_ptr (здесь нет необходимости в shared_ptr, вы не будете делиться pimpl с любыми другими объектами, что обусловлено тем, что scoped_ptr не копируется ), вам нужен только деструктор pimpl, видимый в той точке, где вы вызываете конструктор scoped_ptr.

1009 * Е.Г. *

// in MyClass.h

class Pimpl;

class MyClass 
{ 
private:
    std::auto_ptr<Pimpl> pimpl;

public: 
    MyClass();
};

// Body of these functions in MyClass.cpp

Здесь компилятор сгенерирует деструктор MyClass. Который должен вызывать деструктор auto_ptr. В момент создания деструктора auto_ptr Pimpl является неполным типом. Поэтому в деструкторе auto_ptr при удалении объекта Pimpl он не будет знать, как вызвать деструктор Pimpl.

boost :: scoped_ptr (и shared_ptr) не имеют этой проблемы, потому что когда вы вызываете конструктор scoped_ptr (или метод сброса), он также делает эквивалентный указатель функции, который он будет использовать вместо вызова delete , Ключевым моментом здесь является то, что он создает функцию освобождения, когда Pimpl не является неполным типом. В качестве примечания, shared_ptr позволяет вам указать пользовательскую функцию освобождения , так что вы можете использовать ее для таких вещей, как дескрипторы GDI или для чего-либо еще, что вам захочется - но это излишне для ваших нужд.

Если вы действительно хотите использовать std :: auto_ptr, тогда вам нужно проявить особую осторожность, убедившись, что вы определили свой деструктор MyClass в MyClass.cpp, когда Pimpl полностью определен.

// in MyClass.h

class Pimpl;

class MyClass 
{ 
private:
    std::auto_ptr<Pimpl> pimpl;

public: 
    MyClass();
    ~MyClass();
};

и

// in MyClass.cpp

#include "Pimpl.h"

MyClass::MyClass() : pimpl(new Pimpl(blah))
{
}

MyClass::~MyClass() 
{
    // this needs to be here, even when empty
}

Компилятор будет генерировать код, эффективно уничтожающий все члены MyClass «в» пустом деструкторе. Таким образом, в момент создания деструктора auto_ptr Pimpl больше не является неполным, и компилятор теперь знает, как вызвать деструктор.

Лично я не думаю, что стоит того, чтобы убедиться, что все правильно. Существует также риск того, что кто-нибудь придет позже и исправит код, удалив, казалось бы, избыточный деструктор. Так что для таких вещей просто безопаснее использовать boost :: scoped_ptr.

12 голосов
/ 22 ноября 2008

Я склонен использовать auto_ptr. Обязательно сделайте свой класс некопируемым (объявите личную копию ctor & operator =, или же наследуйте boost::noncopyable). Если вы используете auto_ptr, одна проблема заключается в том, что вам нужно определить не встроенный деструктор, даже если тело пустое. (Это потому, что если вы позволите компилятору сгенерировать деструктор по умолчанию, impl будет неполным типом, когда будет сгенерирован вызов delete impl_, вызывающий неопределенное поведение).

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

4 голосов
/ 26 декабря 2008

boost :: shared_ptr специально разработан для работы с языком pimpl. Одним из основных преимуществ является то, что он позволяет не определять деструктор для класса, держащего pimpl. Политика совместного владения может быть как преимуществом, так и недостатком. Но в дальнейшем вы сможете правильно определить конструктор копирования.

4 голосов
/ 22 ноября 2008

Альтернатива усилению std::auto_ptr равна boost::scoped_ptr. Основное отличие от auto_ptr в том, что boost::scoped_ptr не может быть скопировано.

Подробнее см. на этой странице .

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

Если вам нужен копируемый класс, используйте scoped_ptr, что запрещает копирование, что делает ваш класс трудным для неправильного использования по умолчанию (по сравнению с использованием shared_ptr компилятор не будет выдавать средства копирования самостоятельно; и в случае shared_ptr, если вы не знаете, что делаете (что часто бывает достаточно даже для волшебников), было бы странное поведение, когда внезапно копия чего-либо также изменяет это что-то), а затем переопределить конструктор копирования и назначение копирования:

class CopyableFoo {
public:
    ...
    CopyableFoo (const CopyableFoo&);
    CopyableFoo& operator= (const CopyableFoo&);
private:
    scoped_ptr<Impl> impl_;
};

...
CopyableFoo (const CopyableFoo& rhs)
    : impl_(new Impl (*rhs.impl_))
{}
1 голос
/ 22 ноября 2008

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

Один из вариантов - использовать const auto_ptr. Это работает до тех пор, пока вы можете создать свой pimpl с новым выражением в списке инициализатора и гарантировать, что компилятор не сможет генерировать конструктор копирования по умолчанию и методы присваивания. Не встроенный деструктор для включающего класса все еще должен быть предоставлен.

При прочих равных условиях я бы предпочел реализацию, использующую только стандартные библиотеки, поскольку она делает вещи более переносимыми.

0 голосов
/ 22 ноября 2011

Я был действительно счастлив от impl_ptr Владимира Батова [изменено] . Это действительно упрощает создание pImpl без необходимости явного копирования-конструктора и оператора присваивания.

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

0 голосов
/ 09 марта 2011

shared_ptr намного предпочтительнее auto_ptr для pImpl, потому что ваш внешний класс может внезапно потерять свой указатель при копировании.

С shared_ptr вы можете использовать заранее объявленный тип, чтобы он работал. auto_ptr не позволяет заранее объявленный тип. Также не scoped_ptr, и если ваш внешний класс все равно не подлежит копированию и имеет только один указатель, он также может быть обычным.

Многое можно сказать об использовании навязчивого подсчета ссылок в pImpl и получении внешнего класса для вызова его копии и назначения семантики в его реализации. Предполагая, что это истинная модель поставщика (предоставляет класс), лучше, чтобы поставщик не заставлял пользователя использовать shared_ptr или ту же версию shared_ptr (boost или std).

0 голосов
/ 22 ноября 2008

Не старайся так сильно выстрелить себе в ногу, в C ++ у тебя много возможностей :) Нет никакой необходимости использовать какие-либо авто указатели, так как вы прекрасно знаете, когда ваш объект должен входить и выходить из жизни (в вашем конструкторе (ах) и деструкторе).

Будьте проще.

...