我什么时候使用哪种指针?

时间:2012-01-02 22:58:38

标签: c++ pointers c++11 smart-pointers c++-faq

好的,所以我最后一次以C ++为生,std::auto_ptr是所有标准的lib都可用,boost::shared_ptr风靡一时。我从未真正研究过提供的其他智能指针类型。我知道C ++ 11现在提供了一些类型的提升,但不是全部。

那么有人有一个简单的算法来确定何时使用哪个智能指针?优选地包括关于哑指针(诸如T*的原始指针)和其他增强智能指针的建议。 (this之类的东西会很棒)。

4 个答案:

答案 0 :(得分:174)

共享所有权:
采用的shared_ptrweak_ptr标准与Boost counterparts几乎相同。当您需要共享资源并且不知道哪个资源将是最后一个存活时使用它们。使用weak_ptr观察共享资源而不影响其生命周期,而不是打破周期。通常不应发生shared_ptr的周期 - 两个资源不能互相拥有。

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

接下来,Boost提供intrusive_ptr,如果您的资源已经提供了引用计数管理,并且您希望将其用于RAII原则,那么这是一个轻量级解决方案。标准没有采用这个。

独特的所有权:
Boost还有一个scoped_ptr,它不可复制,您无法指定删除者。类似物std::unique_ptrboost::scoped_ptr,当您需要智能指针时应该是默认选项。它允许您在其模板参数中指定删除器,并且可移动,与boost::scoped_ptr不同。只要您不使用需要可复制类型的操作(显然),它也可以在STL容器中完全使用。

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

请注意,std::auto_ptr仍在标准中,但已弃用§D.10 [depr.auto.ptr]

  

不推荐使用类模板auto_ptr。 [注意:类模板unique_ptr(20.7.1)提供了更好的解决方案。 -end note ]

没有所有权:
对资源使用哑指针(原始指针)或非拥有引用的引用,并且当您知道资源将比引用对象/范围更长时。当您需要可空性或可重置性时,首选引用并使用原始指针。

如果您想要对资源进行非拥有引用,但您不知道该资源是否会比引用该资源的对象更长,请将资源打包到shared_ptr并使用weak_ptr - 您可以使用shared_ptr测试父lock是否还活着,如果资源仍然存在,将返回非{null} shared_ptr。如果要测试资源是否已死,请使用expired。这两者可能听起来相似,但在并发执行方面却非常不同,因为expired仅保证其单个语句的返回值。一个看似无辜的测试,如

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

是潜在的竞争条件。

答案 1 :(得分:127)

确定要使用的智能指针是所有权的问题。在资源管理方面,对象A 拥有对象B,如果它控制对象B的生命周期。例如,成员变量由其各自的对象拥有,因为成员变量的生命周期是绑定的到对象的生命周期。您可以根据对象的拥有方式选择智能指针。

请注意,软件系统中的所有权与所有权是分开的,正如我们在软件之外所想到的那样。例如,一个人可能拥有&#34;他们的家,但这并不一定意味着Person对象可以控制House对象的生命周期。将这些现实世界的概念与软件概念相结合,是将自己编入漏洞的可靠方法。


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

如果您拥有该对象的所有权...
- 如果所有权中没有周期,请使用std::shared_ptr<T> - 如果有周期,则定义一个&#34;方向&#34;并在一个方向使用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为root)的存在,因此使用普通指针。二叉树(具有值语义)拥有其子项的唯一所有权,因此它们是std::unique_ptr

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

此处,列表节点拥有其下一个和上一个列表,因此我们定义一个方向,并使用shared_ptr表示下一个,weak_ptr表示prev可以打破周期。

答案 2 :(得分:19)

除了需要引用计数外,始终使用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[]而不是operator*和{ {1}}。无论如何,你必须自己检查。这使operator->稍微不那么方便用户了,虽然可以说是通用优势而且没有Boost依赖性使得shared_ptrunique_ptr再次成为赢家。

范围指针:与shared_ptr无关,就像unique_ptr一样。

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

其他智能指针仍然有用,例如auto_ptrintrusive_ptr。但是,在一般情况下,它们非常利基并且完全没有必要。

答案 3 :(得分:6)

何时使用unique_ptr的案例:

  • 工厂方法
  • 指针成员(包括pimpl)
  • 在stl包含中存储指针(以避免移动)
  • 使用大型本地动态对象

何时使用shared_ptr的案例:

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

何时使用weak_ptr的案例:

  • 作为一般参考的大型地图(例如所有打开的套接字的地图)

随意编辑并添加更多