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

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

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

归根结底,问题是 C + + 中智能指针的各种实现是什么,它们之间的比较如何?只是简单的利弊或例外情况和陷阱,你可能会认为应该工作的东西。

我已经发布了一些实现,我已经使用或至少粉饰和考虑使用作为一个答案下面和我的理解,他们的差异和相似之处可能不是100% 准确,所以随时检查或纠正我需要的事实。

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

32488 次浏览

C + + 03

std::auto_ptr -也许其中一个原始版本只能提供有限的垃圾收集设施。第一个缺点是,它在销毁时调用 delete,这使得它们不能保存已分配的数组对象(new[])。它拥有指针的所有权,所以两个自动指针不应该包含同一个对象。赋值将转移所有权并将 价值自动指针重置为空指针。这可能会导致最糟糕的缺点: 由于前面提到的无法复制,它们不能在 STL 容器中使用。对于任何用例来说,最后一个打击就是它们将在下一个 C + + 标准中被弃用。

这不是一个智能指针,它实际上是一个设计细节与 std::auto_ptr一起使用,以允许复制和赋值在某些情况下。具体来说,它可以用来转换一个非常数 std::auto_ptr到一个 Lvalue使用科尔文-吉本斯技巧也称为 移动构造函数移动构造函数转移所有权。

相反,也许 std::auto_ptr并不是真的打算用作自动垃圾收集的通用智能指针。我的大多数有限的理解和假设是基于 Herb Sutter 对 auto _ ptr 的有效运用的,我经常使用它,尽管并不总是以最优化的方式。


C + + 11

这是我们的朋友谁将取代 std::auto_ptr它将是非常相似的,除了关键的改进,以纠正的弱点,如与数组,通过私人复制建构子的 Lvalue保护,可用于 STL 容器和算法等。由于它的性能开销和内存占用有限,这是替换原始指针的理想选择,或者更恰当地描述为拥有原始指针。正如“惟一”所暗示的那样,指针只有一个所有者,就像前面的 std::auto_ptr一样。

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

同样基于 TR1和 boost::weak_ptr。这是对 std::shared_ptr拥有的对象的引用,因此,如果 std::shared_ptr引用计数降至零,则不会阻止删除该对象。为了访问原始指针,您首先需要通过调用 lock访问 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更有效,因为它允许对每个对象分配一个堆。< em > (谢谢 Arvid)

这是一个用于数组的 boost::shared_ptr。基本上 new []operator[],当然还有 delete []都是烘烤过的。这可以在 STL 容器中使用,据我所知,尽管不能使用 boost::weak_ptr,但 boost:shared_ptr可以做所有的事情。然而,您可以选择使用 boost::shared_ptr<std::vector<>>来实现类似的功能,并重新获得使用 boost::weak_ptr作为参考的能力。

这是一个用于数组的 boost::scoped_ptr。与 boost::shared_array一样,所有必要的数组优点都被包含在内。这个是不可复制的,因此不能在 STL 容器中使用。我已经发现几乎任何地方你发现自己想要使用这个你可能只是使用 std::vector。我从来没有确定哪个实际上更快或者开销更小,但是这个作用域数组似乎比 STL 向量涉及的要少得多。当您希望在堆栈上保持分配时,可以考虑使用 boost::array


QT

QPointer -在 Qt 4.0中引入了这个“弱”智能指针,它只能用于 QObject和派生类,在 Qt 框架中它是 差不多的所有东西,所以这不是一个限制。但是也有一些限制,即它不提供一个“强”指针,虽然你可以检查底层对象是否有效与 isNull(),你可以发现你的对象被销毁后,你通过检查,特别是在多线程环境。我相信人们认为这是不可取的。

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

QExplicitlySharedDataPointer -非常类似于 QSharedDataPointer,只是它不隐式调用 detach()。我把这个版本称为 QSharedDataPointer的2.0版本,因为在引用计数下降到零之后,控制权的轻微增加,以及确切的分离时间,并不特别值得一个全新的对象。

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

你感觉到一个重复出现的模式了吗?正如 std::weak_ptrboost::weak_ptr一样,当您需要两个智能指针之间的引用时,这会与 QSharedPointer一起使用,否则将导致您的对象永远不会被删除。

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

还有实现基于策略的智能指针的 洛基

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

除了给出的方法外,还有一些以安全为导向的方法:

SaferCPlus

mse::TRefCountingPointer是像 std::shared_ptr一样的引用计数智能指针。不同之处在于,mse::TRefCountingPointer更安全、更小、更快,但是没有任何线程安全机制。它有“非空”和“固定”(不可重定向)版本,可以安全地假定它总是指向一个有效分配的对象。因此,基本上,如果您的目标对象是在异步线程之间共享的,那么使用 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在指向有效对象时具有与原始指针相同的行为,所以它可以被编译时指令“禁用”(自动替换为相应的原始指针) ,允许它用于在调试/测试/测试模式下帮助捕获 bug,同时在发布模式下不会产生开销成本。

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