什么时候使用哪种指针?

好吧,上次我以c++谋生的时候,std::auto_ptr是所有可用的std库,而boost::shared_ptr是最流行的。我从未真正研究过boost提供的其他智能指针类型。我知道c++ 11现在提供了boost提出的一些类型,但不是全部。

那么,有人有一个简单的算法来决定何时使用哪个智能指针吗?最好包括关于哑指针(如T*的原始指针)和其他boost智能指针的建议。(像这样的东西会很好)。

39358 次浏览

除非需要引用计数,否则一直使用unique_ptr<T>,在这种情况下使用shared_ptr<T>(在极少数情况下,使用weak_ptr<T>来防止引用循环)。在几乎所有情况下,可转让的唯一所有权都是可以的。

原始指针:只有当你需要协变返回时才有用,非拥有的指针可能会发生。除此之外,它们并没有太大用处。

数组指针:unique_ptr有一个针对T[]的专门化,它会自动对结果调用delete[],所以你可以安全地执行unique_ptr<int[]> p(new int[42]);shared_ptr你仍然需要一个自定义删除器,但你不需要一个专门的共享或唯一数组指针。当然,这样的东西通常最好用std::vector代替。不幸的是,shared_ptr没有提供数组访问函数,所以你仍然需要手动调用get(),但是unique_ptr<T[]>提供了operator[]而不是T[]0和T[]1。在任何情况下,你都必须检查自己的边界。这使得shared_ptr的用户友好性稍差,尽管可以说通用优势和不依赖Boost使得unique_ptrshared_ptr再次成为赢家。

作用域指针:通过unique_ptr使其不相关,就像auto_ptr一样。

真的没有别的了。在没有移动语义的c++ 03中,这种情况非常复杂,但在c++ 11中,建议非常简单。

还有其他智能指针的用途,如intrusive_ptrinterprocess_ptr。然而,它们是非常小众的,在一般情况下完全不必要。

决定使用哪种智能指针是所有权的问题。当涉及到资源管理时,如果对象A控制对象B的生命周期,则对象A 拥有对象B。例如,成员变量由它们各自的对象拥有,因为成员变量的生命周期与对象的生命周期捆绑在一起。根据对象的归属方式选择智能指针。

请注意,软件系统中的所有权与所有权是分开的,因为我们会在软件之外考虑它。例如,一个人可能“拥有”他们的房子,但这并不一定意味着Person对象可以控制House对象的生命周期。将这些现实世界的概念与软件概念混为一谈,肯定会让你自己陷入困境。


如果你拥有对象的唯一所有权,使用std::unique_ptr<T>

如果你拥有该对象的共享所有权 —如果所有权中没有循环,使用std::shared_ptr<T>.
-如果存在循环,定义一个“方向”,并在一个方向上使用std::shared_ptr<T>,在另一个方向上使用std::weak_ptr<T>

如果对象拥有你,但有可能没有所有者,使用普通指针T*(例如父指针)。

如果对象拥有你(或以其他方式保证存在),则使用引用T&


警告:要注意智能指针的成本。在内存或性能有限的环境中,使用普通指针和更手动的方案来管理内存可能会更有好处。

成本:

  • 如果你有一个自定义删除器(例如,你使用分配池),那么这将引起每个指针的开销,可以通过手动删除轻松避免。
  • std::shared_ptr具有复制时引用计数增量的开销,加上销毁时引用计数递减,然后是删除持有对象的0计数检查。根据实现的不同,这可能会使代码膨胀并导致性能问题。
  • 编译时间。与所有模板一样,智能指针对编译时间的贡献是负面的。

例子:

struct BinaryTree
{
Tree* m_parent;
std::unique_ptr<BinaryTree> m_children[2]; // or use std::array...
};

二叉树不拥有它的父树,但树的存在意味着它的父树的存在(或nullptr作为根),因此使用普通指针。二叉树(具有值语义)拥有它的子树的唯一所有权,所以它们是std::unique_ptr

struct ListNode
{
std::shared_ptr<ListNode> m_next;
std::weak_ptr<ListNode> m_prev;
};

在这里,列表节点拥有它的next和previous列表,所以我们定义了一个方向,并使用shared_ptr表示next,使用weak_ptr表示prev来打破循环。

< p > 共享所有权: < br > 所采用的标准shared_ptrweak_ptr与它们的提高同行几乎相同。当你需要共享一个资源,不知道谁会是最后一个活下来的时候,可以使用它们。使用weak_ptr在不影响其生命周期的情况下观察共享资源,而不是打破循环。带有shared_ptr的循环通常不应该发生——两个资源不能彼此拥有

注意Boost还提供了shared_array,这可能是shared_ptr<std::vector<T> const>的合适替代品。

接下来,Boost提供了intrusive_ptr,这是一个轻量级的解决方案,如果你的资源已经提供了引用计数管理,并且你想采用它来实现RAII原则。这个没有被标准采用。

< p > 独特的所有权: < br > Boost也有一个scoped_ptr,它是不可复制的,你不能为它指定删除器。std::unique_ptrboost::scoped_ptr的类固醇,应该是你的需要智能指针时的默认选择。它允许你在模板参数中指定一个删除符,它是可移动的,不像boost::scoped_ptr。它在STL容器中也完全可用,只要你不使用需要可复制类型的操作(显然)

再次注意,Boost有一个数组版本:scoped_array,标准通过要求std::unique_ptr<T[]>部分特化来统一该版本,该特化将delete[]指针而不是deleteing指针(使用default_deleter)。std::unique_ptr<T[]>也提供了operator[]而不是operator*operator->

注意std::auto_ptr仍然在标准中,但它是弃用§D.10 [depr.auto.ptr] < / p >

类模板auto_ptr已弃用。类模板unique_ptr(20.7.1)提供了一个更好的解决方案。端注]

< p > 没有所有权: < br > 使用哑指针(原始指针)或non-owning引用对资源的引用,当你知道资源会长寿引用对象/作用域时。当需要可空性或可重置性时,优先使用引用并使用原始指针

如果你想要一个对资源的非所有者引用,但你不知道资源是否会比引用它的对象活得更久,将资源打包在shared_ptr中并使用weak_ptr -你可以用lock测试父shared_ptr是否活,如果资源仍然存在,它将返回一个非空的shared_ptr。如果想测试资源是否已死,请使用expired。这两者听起来很相似,但在并发执行时却非常不同,因为expired只保证了该语句的返回值。一个看似无辜的测试

if(!wptr.expired())
something_assuming_the_resource_is_still_alive();

是潜在的竞争条件。

何时使用unique_ptr的情况:

  • 工厂方法
  • 指针的成员(包括pimpl)
  • 在stl容器中存储指针(以避免移动)
  • 使用大型局部动态对象

何时使用shared_ptr的情况:

  • 跨线程共享对象
  • 一般共享对象

何时使用weak_ptr的情况:

  • 充当通用引用的大映射(例如,所有打开套接字的映射)

请随意编辑和添加更多内容