Std:: auto_ptr или boost:: shared_ptr для идиомы pImpl?

При использовании pImpl idiom предпочтительнее использовать 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 < > или есть ситуации, когда требуется альтернативный интеллектуальный указатель boost?

Ответ 1

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

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

например.

// 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 destructor, Pimpl является неполным типом. Таким образом, для деструктора auto_ptr, когда он удаляет объект Pimpl, он не знает, как вызвать деструктор Pimpl.

boost:: scoped_ptr (и shared_ptr) не имеет этой проблемы, потому что, когда вы вызываете конструктор метода scoped_ptr (или метода reset), он также выполняет эквивалент-указатель, который он будет использовать вместо вызывающий удаление. Ключевым моментом здесь является то, что он создает функцию освобождения, когда 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 destructor Pimpl больше не является неполным, и компилятор теперь знает, как вызвать деструктор.

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

Ответ 2

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

Там мало выбора между auto_ptr и указателями форсирования. Я предпочитаю не использовать стилистическую основу, если будет использоваться стандартная альтернатива библиотеки.

Ответ 3

Альтернативой повышения std::auto_ptr является boost::scoped_ptr. Основное отличие от auto_ptr заключается в том, что boost::scoped_ptr не поддается копированию.

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

Ответ 4

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

Ответ 5

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

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

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

Ответ 6

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

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

...
CopyableFoo (const CopyableFoo& rhs)
    : impl_(new Impl (*rhs.impl_))
{}

Ответ 7

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

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

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

Ответ 8

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

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

Ответ 9

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

Держите его простым.