16

当使用pImpl 成语时,最好使用 aboost:shared_ptr而不是 a 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_;
};

[编辑] 使用 std::auto_ptr<> 是否总是安全的,或者是否存在需要替代提升智能指针的情况?

4

9 回答 9

38

你不应该真的为此使用 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 析构函数被实例化的地方,Pimpl 是一个不完整的类型。所以在 auto_ptr 析构函数中,当它删除 Pimpl 对象时,它不知道如何调用 Pimpl 析构函数。

boost::scoped_ptr(和shared_ptr)没有这个问题,因为当你调用scoped_ptr(或reset方法)的构造函数时,它也会使用一个等效的函数指针,而不是调用delete。这里的关键是当 Pimpl 不是不完整类型时,它会实例化释放函数。附带说明一下,shared_ptr 允许您指定自定义解除分配函数,因此您可以将其用于 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 会更安全。

于 2008-11-22T11:57:06.457 回答
12

我倾向于使用auto_ptr. 确保您的类不可复制(声明私有副本 ctor & operator=,否则继承boost::noncopyable)。如果你使用auto_ptr,一个问题是你需要定义一个非内联析构函数,即使主体是空的。(这是因为如果让编译器生成默认析构函数,在生成调用impl时将是一个不完整的类型delete impl_,调用未定义的行为)。

auto_ptr升压指针之间几乎没有选择余地。如果可以使用标准库替代方案,我倾向于不出于文体原因使用 boost。

于 2008-11-22T10:24:05.177 回答
4

的升压替代品std::auto_ptrboost::scoped_ptr. 的主要区别auto_ptr在于boost::scoped_ptr不可复制。

有关详细信息,请参阅此页面。

于 2008-11-22T10:25:36.797 回答
4

boost::shared_ptr 是专门为 pimpl idiom 量身定做的。主要优点之一是它允许不为持有 pimpl 的类定义析构函数。共享所有权政策可能既有优点也有缺点。但在以后的情况下,您可以正确定义复制构造函数。

于 2008-12-26T19:14:06.977 回答
1

如果您真的很迂腐,则无法绝对保证使用auto_ptr成员不需要在使用auto_ptr它的位置完整定义 的模板参数。话虽如此,我从未见过这不起作用。

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

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

于 2008-11-22T11:12:26.220 回答
1

如果你想要一个可复制的类, use scoped_ptr,它禁止复制,因此默认情况下使你的类很难使用错误(与 using 相比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_))
{}
于 2011-08-10T15:02:33.153 回答
0

对于 pImpl,shared_ptr 比 auto_ptr 更可取,因为当你复制它时,你的外部类可能会突然丢失它的指针。

使用 shared_ptr 您可以使用前向声明的类型,这样就可以了。auto_ptr 不允许前向声明的类型。scoped_ptr 也没有,如果你的外部类无论如何都是不可复制的并且只有一个指针,它也可能是一个普通的。

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

于 2011-03-09T17:07:04.903 回答
0

我对Vladimir Batov [修改] 的 impl_ptr感到非常高兴。它使创建 pImpl 变得非常容易,而无需显式地创建复制构造函数和赋值运算符。

我已经修改了原始代码,所以它现在类似于一个 shared_ptr,所以它可以在 Epilog 代码中使用,并且保持快速。

于 2011-11-21T22:27:00.067 回答
-8

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

把事情简单化。

于 2008-11-22T10:20:04.220 回答