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
Не пытайтесь так сильно стрелять в ногу, на С++ у вас много возможностей:)
Нет никакой реальной необходимости использовать автоматические указатели, поскольку вы прекрасно знаете, когда ваш объект должен входить и выходить из жизни (в ваших конструкторах и деструкторах).
Держите его простым.