126

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

这是一个垃圾收集线程的衍生产品,我认为这是一个简单的答案,产生了很多关于一些特定智能指针实现的评论,所以似乎值得开始一篇新文章。

最终的问题是 C++ 中智能指针的各种实现是什么,它们如何比较?只是简单的优点和缺点或例外,以及一些你可能认为应该有效的问题。

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

目标是了解一些新的对象和库,或者纠正我对已经广泛使用的现有实现的使用和理解,并最终为其他人提供体面的参考。

4

3 回答 3

234

C++03

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

std::auto_ptr_ref- 这不是一个智能指针,它实际上是一个设计细节,用于std::auto_ptr在某些情况下允许复制和分配。具体来说,它可用于使用 Colvin-Gibbons 技巧(也称为移动构造函数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,基于此实现,这允许对 a 的非拥有引用boost::shared_ptr。毫不奇怪,您调用lock()访问“强”共享指针并且必须检查以确保它是有效的,因为它可能已经被破坏了。只需确保不要存储返回的共享指针并在完成后立即让它超出范围,否则您将立即回到循环引用问题,您的引用计数将挂起并且对象不会被破坏。

boost::scoped_ptr- 这是一个简单的智能指针类,开销很小,可能是为了更好地替代boost::shared_ptr可用时而设计的。std::auto_ptr尤其是在它不能安全地用作 STL 容器的元素或具有指向同一对象的多个指针这一事实时,它尤其具有可比性。

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

boost::shared_array- 这是一个boost::shared_ptr数组。基本上new [], operator[], 当然delete []是烘焙的。这可以在 STL 容器中使用,据我所知,boost:shared_ptr虽然你不能boost::weak_ptr与这些容器一起使用,但一切都可以。但是,您也可以将 aboost::shared_ptr<std::vector<>>用于类似功能并重新获得boost::weak_ptr用于引用的能力。

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


Qt

QPointer- 在 Qt 4.0 中引入,这是一个“弱”智能指针,仅适用于QObject派生类,在 Qt 框架中它几乎是所有东西,所以这并不是真正的限制。但是存在一些限制,即它不提供“强”指针,尽管您可以检查底层对象是否有效,但isNull()您可以在通过检查后发现您的对象被销毁,尤其是在多线程环境中。我相信 Qt 人们认为这已被弃用。

QSharedDataPointer- 这是一个“强”的智能指针,boost::intrusive_ptr虽然它具有一些内置的线程安全性,但它确实需要您包含引用计数方法(refderef),您可以通过子类化来实现QSharedData。与大部分 Qt 一样,对象最好通过充分的继承和子类化来使用,这似乎是预期的设计。

QExplicitlySharedDataPointer- 非常相似,QSharedDataPointer只是它没有隐式调用detach(). 我将这个版本称为 2.0 版本,QSharedDataPointer因为在引用计数降至零之后,关于何时分离的控制的轻微增加并不是特别值得一个全新的对象。

QSharedPointer- 原子引用计数、线程安全、可共享指针、自定义删除(数组支持),听起来就像智能指针应该具备的一切。这是我在 Qt 中主要用作智能指针的东西,我发现它可以与boost:shared_ptrQt 中的许多对象相比,尽管开销可能要大得多。

QWeakPointer- 你感觉到重复出现的模式了吗?就像std::weak_ptrand boost::weak_ptrthis 与QSharedPointer当您需要两个智能指针之间的引用时一起使用,否则会导致您的对象永远不会被删除。

QScopedPointer- 这个名称也应该看起来很熟悉,实际上它boost::scoped_ptr与 Qt 版本的共享指针和弱指针不同。它的功能是提供单个所有者智能指针,而不会产生开销,QSharedPointer使其更适合兼容性、异常安全代码以及您可能使用std::auto_ptrboost::scoped_ptr用于的所有事情。

于 2011-02-17T08:44:16.110 回答
5

还有Loki实现了基于策略的智能指针。

关于基于策略的智能指针的其他参考,解决了许多编译器对空基优化以及多重继承的支持不佳的问题:

于 2011-09-02T23:42:51.443 回答
1

除了给定的之外,还有一些以安全为导向的:

SaferCPlusPlus

mse::TRefCountingPointer是一个引用计数智能指针,如std::shared_ptr. 不同之处在于mse::TRefCountingPointer更安全、更小、更快,但没有任何线程安全机制。它有“非空”和“固定”(不可重定向)版本,可以安全地假定它们始终指向一个有效分配的对象。因此,基本上,如果您的目标对象在异步线程之间共享,则使用std::shared_ptr,否则mse::TRefCountingPointer更佳。

mse::TScopeOwnerPointer与 类似boost::scoped_ptr,但与mse::TScopeFixedPointer“强-弱”指针关系类似std::shared_ptr和结合使用std::weak_ptr

mse::TScopeFixedPointer指向在堆栈上分配的对象,或者其“拥有”指针在堆栈上分配。它(故意)限制了它的功能,以增强编译时安全性而没有运行时成本。“范围”指针的要点本质上是识别一组足够简单和确定性的环境,以至于不需要(运行时)安全机制。

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

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

于 2016-02-13T17:54:44.677 回答