用于pImpl成语的std :: auto_ptr或boost :: shared_ptr?
-
10-07-2019 - |
题
使用 pImpl成语时,最好使用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;还是有需要替代升压智能指针的情况?
解决方案
你不应该为此使用std :: auto_ptr。在声明std :: auto_ptr时,析构函数将不可见,因此可能无法正确调用它。这假设您正在声明您的pImpl类,并在另一个文件中的构造函数内创建实例。
如果您使用 boost :: scoped_ptr (这里不需要shared_ptr,你不会与任何其他对象共享pimpl,这是由scoped_ptr强制执行的 noncopyable ),您只需要在调用scoped_ptr构造函数时可见的pimpl析构函数。
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允许您指定自定义释放功能,所以你可以用它来处理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可以更安全。
其他提示
我倾向于使用auto_ptr
。一定要使你的类不可复制(声明私有拷贝ctor <!> amp; operator =,否则继承boost::noncopyable
)。如果使用impl
,则需要定义一个非内联析构函数,即使正文为空。 (这是因为如果让编译器生成默认的析构函数,则在生成对delete impl_
的调用时,<=>将是一个不完整的类型,从而调用未定义的行为)。
在<=> <!>放大器之间几乎没有选择;提升指针。如果使用标准库替代方案,我倾向于不在风格上使用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比pImpl的auto_ptr要好得多,因为你的外部类在复制时会突然失去指针。
使用shared_ptr,您可以使用向前声明的类型,以便工作。 auto_ptr不允许向前声明的类型。 scoped_ptr也没有,如果你的外部类无论如何都是不可复制的,只有一个指针,它也可能是常规的。
在pImpl中使用侵入式引用计数并获取外部类来调用其副本并在其实现中分配语义,还有很多要说的。假设这是一个真正的供应商(提供类)模型,供应商不会强迫用户使用shared_ptr,或者使用相同版本的shared_ptr(boost或std)。
我真的很高兴的rel =“nofollow”> impl_ptr。它使创建pImpl变得非常容易,而无需制作显式的复制构造函数和赋值运算符。
我修改了原始代码,因此它现在类似于shared_ptr,因此可以在epilog代码中使用,并且保持快速。
不要那么努力地用脚射击自己,用C ++你有很多机会:) 没有必要使用任何一个自动指针,因为你完全知道你的对象何时进出生命(在你的构造函数和析构函数中)。
保持简单。