【问题标题】:Which kind of pointer do I use when?我什么时候使用哪种指针?
【发布时间】:2012-02-01 03:13:07
【问题描述】:

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

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

【问题讨论】:

  • 我真的希望有人想出一个很好的方便流程图,比如this STL selection flowchart
  • @Als:哦,这确实是一个不错的!我FAQ了。
  • @Deduplicator 这甚至不接近重复。链接的问题是“我什么时候应该使用 a 智能指针”,这个问题是“我什么时候使用 这些 智能指针?”即这是对标准智能指针的不同用途进行分类。链接的问题不这样做。差别看似很小,其实很大。

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


【解决方案1】:

共享所有权:
采用的标准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_ptr 是类固醇上的 boost::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]

类模板auto_ptr 已弃用。 [ 注意: 类模板unique_ptr (20.7.1) 提供了更好的解决方案。 ——尾注 ]

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

如果您想要对资源的非拥有引用,但您不知道该资源是否会比引用它的对象寿命长,请将资源打包在 shared_ptr 中并使用 weak_ptr - 您可以测试如果父 shared_ptrlock 一起存在,则如果资源仍然存在,它将返回非 null 的 shared_ptr。如果要测试资源是否死亡,请使用expired。两者可能听起来很相似,但在并发执行方面却有很大不同,因为expired 只保证它对单个语句的返回值。一个看似无辜的测试,如

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

是一个潜在的竞争条件。

【讨论】:

  • 在没有所有权的情况下,您可能应该更喜欢对指针的引用,除非您不需要所有权在引用不会削减它的情况下的可重置性,即使那样你可能想要考虑将原始对象重写为shared_ptr,将非拥有指针重写为weak_ptr...
  • 我的意思不是引用指针,而是引用而不是指针。如果没有所有权,除非您需要可重置性(或可空性,但无法重置的可空性将非常有限),您可以首先使用普通引用而不是指针。
  • @David:啊,我明白了。 :) 是的,参考也不错,在这种情况下,我个人也更喜欢它们。我会添加它们。
  • @Xeo: shared_array<T>shared_ptr<T[]> 的替代品,而不是shared_ptr<vector<T>>:它不能增长。
  • @GregroyCurrie:那是……正是我写的?我说这是潜在竞争条件的一个例子。
【解决方案2】:

决定使用什么智能指针是一个所有权的问题。在资源管理方面,如果对象 A 控制了对象 B 的生命周期,则它拥有对象 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;
};

这里,列表节点拥有它的下一个和上一个列表,因此我们定义了一个方向并使用shared_ptr 表示下一个,使用weak_ptr 表示上一个来打破循环。

【讨论】:

  • 对于二叉树的例子,有些人会建议使用shared_ptr&lt;BinaryTree&gt; 表示孩子,weak_ptr&lt;BinaryTree&gt; 表示父关系。
  • @DavidRodríguez-dribeas:这取决于树是否具有值语义。如果即使源树被破坏,人们仍要在外部引用您的树,那么是的,共享/弱指针组合是最好的。
  • 如果一个对象拥有你并且保证存在,那么为什么不引用。
  • 如果你使用参考,你永远不能改变父级,这可能会也可能不会妨碍设计。对于平衡树,这会阻碍。
  • +1 但您应该在第一行添加“所有权”的定义。我经常发现自己必须清楚地说明这是关于对象的生与死,而不是更具体领域意义上的所有权。
【解决方案3】:

一直使用unique_ptr&lt;T&gt;,除非您需要引用计数,在这种情况下使用shared_ptr&lt;T&gt;(在极少数情况下,使用weak_ptr&lt;T&gt; 以防止引用循环)。几乎在所有情况下,可转让的唯一所有权都很好。

原始指针:仅当您需要协变返回时才有效,非拥有指针可能会发生。否则它们并不是非常有用。

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

作用域指针:unique_ptr 使其无关紧要,就像 auto_ptr

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

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

【讨论】:

  • 还有,用于迭代的原始指针。对于输出参数缓冲区,缓冲区归调用者所有。
  • 嗯,按照我的理解,这是协变回报和非拥有的情况。如果您的意思是联合而不是交集,重写可能会很好。我还要说迭代也值得特别提及。
  • std::unique_ptr&lt;T[]&gt; 提供 operator[] 而不是 operator*operator-&gt;。确实,您仍然需要自己进行边界检查。
【解决方案4】:

何时使用unique_ptr的案例:

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

何时使用shared_ptr的案例:

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

何时使用weak_ptr的案例:

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

随意编辑和添加更多内容

【讨论】:

  • 我实际上更喜欢你的回答,因为你给出了场景。
猜你喜欢
  • 2010-10-14
  • 1970-01-01
  • 1970-01-01
  • 2010-11-02
  • 2010-09-11
相关资源
最近更新 更多