何时使用哪种指针?

Which kind of pointer do I use when?

提问人:sbi 提问时间:1/3/2012 最后编辑:Communitysbi 更新时间:9/16/2018 访问量:40849

问:

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

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

C ++11 智能指针 C++-FAQ

评论

0赞 Martin York 1/3/2012
另请参阅 std::auto_ptr 到 std::unique_ptr
2赞 Alok Save 1/3/2012
我真的希望有人想出一个很好的方便的流程图,比如这个 STL 选择流程图
1赞 sbi 1/3/2012
@Als:噢,这真是太好了!我FAQ化了它。
6赞 Rapptz 6/5/2014
@Deduplicator 这甚至还不是复制品。链接的问题说“我什么时候应该使用智能指针”,这个问题是“我什么时候使用这些智能指针?”,即这个问题对标准智能指针的不同用途进行分类。链接的问题不会这样做。差异看似很小,但差异很大。

答:

20赞 Puppy 1/3/2012 #1

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

原始指针:仅当您需要协变返回时才好,非拥有指向可能会发生。否则,它们就没有太大的用处。

数组指针:具有自动调用结果的专用化,因此您可以安全地执行例如。 您仍然需要一个自定义删除器,但不需要专门的共享或唯一数组指针。当然,这些东西通常最好用无论如何来代替。不幸的是,它没有提供数组访问功能,因此您仍然需要手动调用 ,但提供而不是 和。无论如何,您都必须对自己进行边界检查。这使得用户友好性略有下降,尽管可以说是通用优势和没有 Boost 依赖性,并且再次成为赢家。unique_ptrT[]delete[]unique_ptr<int[]> p(new int[42]);shared_ptrstd::vectorshared_ptrget()unique_ptr<T[]>operator[]operator*operator->shared_ptrunique_ptrshared_ptr

作用域指针:由 使 变得无关紧要,就像 一样。unique_ptrauto_ptr

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

其他智能指针仍有用途,如 或 。但是,它们在一般情况下非常小众,完全没有必要。intrusive_ptrinterprocess_ptr

评论

0赞 Ben Voigt 1/3/2012
此外,迭代的原始指针。对于输出参数缓冲区,缓冲区归调用方所有。
0赞 Ben Voigt 1/3/2012
嗯,我读到的方式是,这种情况既是协变回报又是非拥有。如果你指的是联合而不是交集,重写可能会很好。我还想说迭代也值得特别一提。
2赞 Xeo 1/3/2012
std::unique_ptr<T[]>提供而不是 和 。确实,您仍然需要自己进行绑定检查。operator[]operator*operator->
128赞 Peter Alexander 1/3/2012 #2

决定使用哪个智能指针是一个所有权问题。在资源管理方面,如果对象 A 控制对象 B 的生存期,则对象 A 拥有对象 B。例如,成员变量由其各自的对象拥有,因为成员变量的生存期与对象的生存期相关联。您可以根据对象的拥有方式来选择智能指针。

请注意,软件系统中的所有权与所有权是分开的,因为我们认为它是在软件之外的。例如,一个人可能“拥有”他们的家,但这并不一定意味着对象可以控制对象的生命周期。将这些现实世界的概念与软件概念混为一谈,是将自己编程成洞的可靠方法。PersonHouse


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

如果您共享了对象的所有权...
- 如果所有权没有周期,请使用 .
- 如果有循环,请定义一个“方向”,并在一个方向和另一个方向上使用。
std::shared_ptr<T>std::shared_ptr<T>std::weak_ptr<T>

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

如果对象拥有您(或以其他方式保证存在),请使用 references 。T&


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

费用:

  • 如果您有自定义删除程序(例如,您使用分配池),那么这将产生每个指针的开销,而手动删除可以很容易地避免这些开销。
  • std::shared_ptr具有复制时引用计数递增的开销,加上销毁时的递减,然后是删除保留对象的 0 计数检查。根据实现的不同,这可能会使代码膨胀并导致性能问题。
  • 编译时。与所有模板一样,智能指针对编译时间有负面影响。

例子:

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

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

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

在这里,列表节点拥有它的 next 和 previous 列表,因此我们定义了一个方向,并使用 next 和 for prev 来打破循环。shared_ptrweak_ptr

评论

3赞 David Rodríguez - dribeas 1/3/2012
对于二叉树的例子,有些人建议将二叉树用于子关系和父关系。shared_ptr<BinaryTree>weak_ptr<BinaryTree>
0赞 Peter Alexander 1/3/2012
@DavidRodríguez-dribeas:这取决于树是否具有值语义。如果人们要在源树被破坏后从外部引用你的树,那么是的,共享/弱指针组合将是最好的。
0赞 Martin York 1/3/2012
如果一个对象拥有你并保证存在,那么为什么不是一个引用。
1赞 Mooing Duck 1/3/2012
如果使用引用,则永远无法更改父项,这可能会也可能不会妨碍设计。对于平衡树木来说,这会阻碍。
3赞 Klaim 1/4/2012
+1,但您应该在第一行添加“所有权”的定义。我经常发现自己必须明确声明,这是关于对象的生死,而不是更特定于领域含义的所有权。
187赞 Xeo 1/3/2012 #3

共享所有权:
采用的标准与 Boost 标准几乎相同。当您需要共享资源并且不知道哪一个会是最后一个活着时,请使用它们。用于观察共享资源而不影响其生命周期,而不是中断循环。循环通常不应该发生 - 两个资源不能相互拥有。
shared_ptrweak_ptrweak_ptrshared_ptr

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

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

唯一所有权:
Boost 还有一个不可复制的scoped_ptr,您不能为其指定删除程序。 正在服用类固醇,当您需要智能指针时,应该是您的默认选择。它允许您在其模板参数中指定删除器,并且是可移动的,这与 .它也可以完全用于 STL 容器,只要您不使用需要可复制类型的操作(显然)。
std::unique_ptrboost::scoped_ptrboost::scoped_ptr

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

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

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

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

如果您想要对资源的非拥有引用,但您不知道该资源是否会比引用它的对象更长,请将资源打包到 a 中并使用 - 您可以测试父项是否处于活动状态,如果资源仍然存在,它将返回非 null 的 a。如果要测试资源是否已失效,请使用 .这两者听起来可能很相似,但在面对并发执行时却大不相同,因为只保证该语句的返回值。一个看似无辜的测试,比如shared_ptrweak_ptrshared_ptrlockshared_ptrexpiredexpired

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

是一种潜在的争用条件。

评论

1赞 David Rodríguez - dribeas 1/3/2012
在没有所有权的情况下,您可能更喜欢对指针的引用,除非您不需要所有权可重置性,因为引用不会削减它,即使这样,您可能也要考虑将原始对象重写为 a,将非拥有指针重写为 ...shared_ptrweak_ptr
2赞 David Rodríguez - dribeas 1/3/2012
我不是说引用指,而是引用而不是指针。如果没有所有权,除非您需要可重置性(或可空性,但无法重置的可空性将非常有限),否则首先可以使用普通引用而不是指针。
1赞 Xeo 1/3/2012
@David:啊,我明白了。:)是的,参考资料对此还不错,在这种情况下,我个人也更喜欢它们。我会添加它们。
1赞 R. Martinho Fernandes 1/4/2012
@Xeo:是 not 的替代方法:它不能增长。shared_array<T>shared_ptr<T[]>shared_ptr<vector<T>>
2赞 Xeo 6/9/2017
@GregroyCurrie: 那是......我到底写了什么?我说这是潜在竞争条件的一个例子。
12赞 user406009 1/3/2012 #4

何时使用案例:unique_ptr

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

何时使用案例:shared_ptr

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

何时使用案例:weak_ptr

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

随意编辑和添加更多内容

评论

2赞 Nicholas Humphrey 2/26/2019
我实际上更喜欢你的回答,因为你给出了场景。