有哪些C ++智能指针实现?


121

比较,优点,缺点以及何时使用?

这是从垃圾回收线程中衍生出来的中,我认为这是一个简单的答案,它生成了一些有关某些特定智能指针实现的评论,因此似乎值得开始新文章。

最终的问题是,C ++中智能指针的各种实现方式是什么,它们如何进行比较?只是简单的利弊或异常,否则您可能会认为应该起作用。

我已经发布了一些我已经使用或至少被掩盖并考虑使用的实现作为以下答案,并且我了解它们的差异和相似性可能不是100%准确的,因此可以根据需要随时进行事实检查或纠正。

目的是学习一些新的对象和库,或者更正我的用法以及对已经广泛使用的现有实现的理解,并最终为其他人提供不错的参考。


5
我认为应该将其重新发布为对该问题的答案,并将该问题变为实际问题。否则,我认为人们会将这个问题视为“不是一个真正的问题”。
斯特拉格2011年

3
还有各种其他智能指针,例如ATL智能指针OpenSceneGraph的osg::ref_ptr
James McNellis 2011年

11
这里有问题吗?
科迪·格雷

6
我认为您误会了std::auto_ptrstd::auto_ptr_ref是的设计细节std::auto_ptrstd::auto_ptr与垃圾回收无关,主要目的是允许异常安全地转移所有权,尤其是在函数调用和函数返回情况下。std::unique_ptr只能解决您使用标准容器引用的“问题”,因为C ++已更改为允许移动和复制之间的区别,并且标准容器也已更改以利用此优势。
CB Bailey

3
您说您不是智能指针方面的专家,但是您的摘要非常详尽且正确(除了关于auto_ptr_ref实现细节的小错误)。不过,我同意您应该将此作为答案发布,并将问题重新表述为实际问题。这可以作为将来的参考。
康拉德·鲁道夫

Answers:


231

C ++ 03

std::auto_ptr-也许患有初稿综合症的原始物之一只能提供有限的垃圾收集设施。第一个缺点是它要求delete销毁,使它们无法容纳数组分配的对象(new[])。它拥有指针的所有权,因此两个自动指针不应包含相同的对象。分配将转移所有权,并将右值自动指针重置为空指针。这可能导致最严重的缺点;由于上述无法复制,因此无法在STL容器中使用它们。对所有用例的最后一击是,它们计划在C ++的下一个标准中弃用。

std::auto_ptr_ref-这不是一个智能指针,它实际上是一个设计细节,用于std::auto_ptr在某些情况下进行复制和分配。具体地说,它可用于一非const转换std::auto_ptr到一个左值使用科尔-长臂猿特技也称为移动构造函数,以转移所有权。

相反,也许std::auto_ptr并不是真正打算用作自动垃圾收集的通用智能指针。我大多数有限的理解和假设都是基于Herb Sutter的对auto_ptr的有效使用,尽管并非总是以最优化的方式进行,但我确实定期使用它。


C ++ 11

std::unique_ptr-这是我们的朋友,将取代std::auto_ptr它,它与以前非常相似,只是在关键方面进行了改进,以纠正std::auto_ptr诸如处理数组,通过私有副本构造函数进行左值保护,可以与STL容器和算法一起使用等弱点。由于这是性能开销并且内存占用空间有限,这是替换原始指针的理想选择,或者更恰当地描述为拥有原始指针。正如“唯一”所暗示的,与前一个指针一样,指针也只有一个所有者std::auto_ptr

std::shared_ptr-我相信这是基于TR1的,boost::shared_ptr但也进行了改进,包括别名和指针算法。简而言之,它将引用计数的智能指针包装在动态分配的对象周围。由于“共享”表示当最后一个共享指针的最后一个引用超出范围时,该指针可以由多个共享指针拥有,那么该对象将被适当删除。这些也是线程安全的,并且在大多数情况下可以处理不完整的类型。std::make_shared可用于std::shared_ptr使用默认分配器有效地构造一个堆分配的。

std::weak_ptr-同样基于TR1和boost::weak_ptr。这是对a拥有的对象的引用,std::shared_ptr因此,如果std::shared_ptr引用计数降至零,则不会阻止删除该对象。为了获得对原始指针的访问,您首先需要std::shared_ptr通过调用来访问,如果拥有的指针已过期并且已经被破坏lock,则它将返回一个空std::shared_ptr。这对于避免使用多个智能指针时不确定的挂起引用计数非常有用。


促进

boost::shared_ptr-可能是最容易在各种情况(STL,PIMPL,RAII等)中使用的,这是一个共享的引用计数智能指针。在某些情况下,我已经听到了一些有关性能和开销的投诉,但是我一定忽略了它们,因为我不记得争论了什么。显然,它很受欢迎,已成为未决的标准C ++对象,并且没有想到关于智能指针的规范方面的缺点。

boost::weak_ptr-非常类似于之前的描述std::weak_ptr,基于此实现,它允许对进行非所有权引用boost::shared_ptr。您毫不奇怪地调用它lock()来访问“强”共享指针,并且必须检查以确保其有效,因为它可能已经被销毁。只要确保不存储返回的共享指针,并在使用完它后立即使其超出范围,否则您将回到循环引用问题,在该循环引用问题中,引用计数将挂起并且对象不会被破坏。

boost::scoped_ptr-这是一个简单的智能指针类,几乎没有开销,可能是为boost::shared_ptr在使用时提供更好的性能而设计的。它与std::auto_ptr以下事实特别具有可比性:它不能安全地用作STL容器的元素或不能使用多个指向同一对象的指针。

boost::intrusive_ptr-我从没有使用过它,但是据我了解,它旨在用于创建自己的智能指针兼容类。您需要自己实现引用的计数,如果您希望类是通用的,还需要实现一些方法,此外,您还必须实现自己的线程安全性。从好的方面来说,这可能为您提供了最自定义的选择和选择您想要的“聪明”程度的方式。intrusive_ptr通常比效率更高,shared_ptr因为它允许您为每个对象分配一个堆。(感谢Arvid)

boost::shared_array-这是一个boost::shared_ptr数组。基本上new []operator[]和当然delete []是烘烤的。可以在STL容器中使用它,据我所知boost:shared_ptr,尽管您不能使用boost::weak_ptr它们,但它所做的一切。但是,您也可以将a boost::shared_ptr<std::vector<>>用于类似功能,并重新获得boost::weak_ptr用于引用的功能。

boost::scoped_array-这是一个boost::scoped_ptr数组。与boost::shared_array所有必需的数组一样,它们也可以烘烤。这是不可复制的,因此不能在STL容器中使用。我发现几乎您发现自己想使用它的任何地方都可以使用std::vector。我从来没有确定哪个实际上更快或更少开销,但是这个作用域数组似乎比STL向量要少得多。当您想在堆栈上保留分配时,请考虑boost::array使用。


t

QPointer-在Qt 4.0中引入了一个“弱”智能指针,它仅适用于QObject和派生类,在Qt框架中几乎所有东西都这样,因此这实际上不是一个限制。但是存在局限性,即它不提供“强”指针,尽管您可以检查基础对象是否有效,isNull()但在通过该检查后,尤其是在多线程环境中,可能会发现对象被破坏。我相信Qt人们认为这已经过时了。

QSharedDataPointer-这是一个“强大”的智能指针,boost::intrusive_ptr尽管它具有一些内置的线程安全性,但潜在地可以与之媲美,但它确实需要您包括引用计数方法(refderef),您可以通过子类化来实现QSharedData。与许多Qt一样,最好通过充分的继承来使用对象,并将所有子类都继承为预期的设计。

QExplicitlySharedDataPointer-与QSharedDataPointer它非常相似,只是它不会隐式调用detach()。我将这个版本QSharedDataPointer称为2.0版,因为在引用计数降至零后确切何时分离的控制稍微增加了,特别不值得使用一个全新的对象。

QSharedPointer-原子引用计数,线程安全,可共享指针,自定义删除(数组支持),听起来像智能指针应该具备的所有功能。这就是我最初在Qt中用作智能指针的原因,boost:shared_ptr尽管与Qt中的许多对象一样,其开销可能要大得多,但我发现它具有可比性。

QWeakPointer-您是否感觉到重复出现的模式?就像当需要在两个智能指针之间进行引用时std::weak_ptrboost::weak_ptr这将一起使用QSharedPointer,否则这将导致您的对象永远不会被删除。

QScopedPointer-这个名称也应该看起来很熟悉,实际上实际上是基于boost::scoped_ptrQt版本的共享指针和弱指针。其功能是提供一个单一的所有者智能指针没有的开销QSharedPointer,这使得它更适合的兼容性,异常安全的代码,你可以使用的东西std::auto_ptrboost::scoped_ptr进行。


1
我认为有两件事值得一提:intrusive_ptr通常比效率更高shared_ptr,因为它允许您为每个对象分配一个堆。shared_ptr通常情况下,将为参考计数器分配一个单独的小堆对象。std::make_shared可以用来同时兼顾两个方面。shared_ptr仅分配一个堆。
Arvid

我可能有一个不相关的问题:是否可以通过仅用shared_ptrs 替换所有指针来实现垃圾回收?(不计算解析循环引用)
赛斯·卡内基

@Seth Carnegie:并非所有的指针都将指向免费商店中分配的东西。
in silico 2011年

2
@the_mandrill但是,如果拥有类的析构函数是在与客户端代码不同的转换单元(.cpp文件)中定义的,则该函数将在Pimpl习惯用法中给出。因为此翻译单元通常知道Pimpl的完整定义,因此其析构函数(当销毁auto_ptr时)会正确销毁Pimpl。当我看到这些警告时,我也对此感到担心,但是我尝试了一下,它就起作用了(Pimpl的析构函数被调用了)。附言:请使用@语法查看我的任何回复。
Christian Rau

2
通过向文档添加适当的链接来增加列表的实用性。
ulidtko


1

除了给定的安全性之外,还有一些面向安全性的安全性:

SaferCPlusPlus

mse::TRefCountingPointer是引用计数的智能指针,如std::shared_ptr。区别在于mse::TRefCountingPointer更安全,更小,更快,但没有任何线程安全机制。它具有“ not null”和“ fixed”(不可重定位)版本,可以安全地假定它们始终指向有效分配的对象。因此,基本上,如果您的目标对象在异步线程之间共享,则使用std::shared_ptr,否则将mse::TRefCountingPointer是最佳选择。

mse::TScopeOwnerPointer类似boost::scoped_ptr的,但结合工作与mse::TScopeFixedPointer在“强弱”指针的关系有点像std::shared_ptrstd::weak_ptr

mse::TScopeFixedPointer指向在堆栈上分配的对象,或者指向其“拥有”指针在堆栈上分配的对象。它(有意地)在功能上受到限制,以在不增加运行时成本的情况下增强编译时安全性。“作用域”指针的要点本质上是要识别一组足够简单和确定性的情况,从而无需(运行时)安全机制。

mse::TRegisteredPointer行为类似于原始指针,只是在销毁目标对象时其值会自动设置为null_ptr。在大多数情况下,它可以用作原始指针的常规替代。像原始指针一样,它没有任何内在的线程安全性。但是作为交换,它毫无问题地针对在堆栈上分配的对象(并获得相应的性能优势)。启用运行时检查后,可以安全地访问该指针。因为mse::TRegisteredPointer在指向有效对象时与原始指针具有相同的行为,因此可以使用编译时指令对其进行“禁用”(自动替换为相应的原始指针),从而使其可用于捕获调试/测试中的错误。 / beta模式,而在发布模式下则不会产生任何开销。

是一篇描述为什么以及如何使用它们的文章。(注意,无耻的插头。)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.