Вопрос

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

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

Это было полезно?

Решение

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

Если вы используете boost::scoped_ptr (Здесь нет необходимости в Shared_ptr, вы не будете использовать Pimpl совместно с какими-либо другими объектами, и это обеспечивается за счет того, чтоscoped_ptr некопируемый), вам нужен только деструктор 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 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.

Другие советы

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

Нет большого выбора между <=> & amp; указатели повышения. Я склонен не использовать boost по стилистическим соображениям, если подойдет стандартная библиотека.

Повышенная альтернатива std::auto_ptr - boost::scoped_ptr. Основное отличие от auto_ptr в том, что <=> не может быть скопировано.

См. эту страницу для получения дополнительной информации.

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

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

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

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

Если вам нужен копируемый класс, используйте scoped_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_))
{}

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top