用于pImpl成语的std :: auto_ptr或boost :: shared_ptr?

时间:2008-11-22 10:03:35

标签: c++ boost stl shared-ptr auto-ptr

使用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_;
};

[编辑]使用std :: auto_ptr&lt;&gt;是否总是安全的还是有需要替代升压智能指针的情况?

9 个答案:

答案 0 :(得分:38)

你不应该为此使用std :: auto_ptr。在声明std :: auto_ptr时,析构函数将不可见,因此可能无法正确调用它。这假设您正在声明您的pImpl类,并在另一个文件中的构造函数内创建实例。

如果您使用boost::scoped_ptr(此处不需要shared_ptr,您将不会与任何其他对象共享pimpl,这由scoped_ptr强制执行noncopyable),您只需要pimpl析构函数在您调用scoped_ptr构造函数时可见。

E.g。

// 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(或reset方法)的构造函数时,它也会使一个函数指针等效,它将使用而不是调用delete 。这里的关键点是,当Pimpl不是不完整类型时,它会实例化释放函数。作为旁注,shared_ptr允许您指定custom deallocation函数,因此您可以将它用于GDI句柄或其他任何您想要的东西 - 但这对您的需求来说太过分了。

如果你真的想使用std :: auto_ptr,那么你需要特别注意确保在完全定义Pimpl时在MyClass.cpp中定义你的MyClass析构函数。

// 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可以更安全。

答案 1 :(得分:12)

我倾向于使用auto_ptr。一定要使你的类不可复制(声明私有拷贝ctor&amp; operator =,否则继承boost::noncopyable)。如果你使用auto_ptr,一个皱纹就是你需要定义一个非内联析构函数,即使正文是空的。 (这是因为如果让编译器生成默认的析构函数,那么在生成对impl的调用时,delete impl_将是一个不完整的类型,从而调用未定义的行为)。

auto_ptr&amp;之间几乎无法选择。提升指针。如果使用标准库替代方案,我倾向于不在风格上使用boost。

答案 2 :(得分:4)

std::auto_ptr的提升替代方案为boost::scoped_ptr。与auto_ptr的主要区别在于boost::scoped_ptr是不可复制的。

有关详细信息,请参阅this page

答案 3 :(得分:4)

boost :: shared_ptr专为pimpl习语而设计。其中一个主要优点是它不允许为持有pimpl的类定义析构函数。共享所有权政策可能既有利也有劣势。但在以后的情况下,您可以正确定义复制构造函数。

答案 4 :(得分:1)

如果你真的很迂腐,那么使用auto_ptr成员并不能绝对保证auto_ptr模板参数在使用时的完整定义。话虽如此,我从未见过这不起作用。

一种变体是使用const auto_ptr。只要您可以在初始化列表中使用新表达式构造“pimpl”,并确保编译器无法生成默认的复制构造函数和赋值方法,这就可以正常工作。仍然需要提供封闭类的非内联析构函数。

在其他条件相同的情况下,我倾向于仅使用标准库的实现,因为它可以使事物更便携。

答案 5 :(得分:1)

如果你想要一个可复制的类,请使用禁止复制的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_))
{}

答案 6 :(得分:0)

shared_ptr比pImpl的auto_ptr要好得多,因为你的外部类在复制时会突然失去指针。

使用shared_ptr,您可以使用向前声明的类型,以便工作。 auto_ptr不允许向前声明的类型。 scoped_ptr也没有,如果你的外部类无论如何都是不可复制的,只有一个指针,它也可能是常规的。

在pImpl中使用侵入式引用计数并获取外部类来调用其副本并在其实现中分配语义,还有很多要说的。假设这是一个真正的供应商(提供类)模型,供应商不会强迫用户使用shared_ptr,或者使用相同版本的shared_ptr(boost或std)。

答案 7 :(得分:0)

我对impl_ptr by Vladimir Batov [modified]非常满意。它使创建pImpl变得非常容易,而无需制作显式的复制构造函数和赋值运算符。

我修改了原始代码,因此它现在类似于shared_ptr,因此可以在epilog代码中使用,并且保持快速。

答案 8 :(得分:-8)

不要那么努力地用脚射击自己,用C ++你有很多机会:) 没有必要使用任何一个自动指针,因为你完全知道你的对象何时进出生命(在你的构造函数和析构函数中)。

保持简单。