何时使用哪种指针?


228

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

那么,有人可以通过简单的算法来确定何时使用哪个智能指针吗?最好包括有关哑指针(原始指针,如T*)和其他Boost智能指针的建议。(喜欢的东西将是巨大的)。



1
我真的希望有人提出像这样的STL选择流程图这样的便捷流程图
Alok保存

1
@Als:哦,那确实是一个不错的选择!我对其进行了常见问题解答。
2012年

6
@Deduplicator这几乎不是重复项。该链接的问题说:“当我应该使用一个智能指针”,这一问题是“什么时候使用这些智能指针?” 即,这是对标准智能指针的不同用途进行分类。链接的问题不会这样做。差异似乎很小,但差异很大。
拉普兹2014年

Answers:


183

共享所有权:
shared_ptrweak_ptr采用的标准是几乎一样的升压同行。当您需要共享资源并且不知道哪个资源将成为最后一个存在时,请使用它们。使用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类固醇,应该是你当你需要一个智能指针默认选择。它允许您在其模板参数中指定一个deleteer,并且可以移动,而不是boost::scoped_ptr。只要您不使用需要可复制类型的操作(显然),它在STL容器中也可以完全使用。

再次注意,Boost具有一个数组版本:scoped_array,该标准通过要求std::unique_ptr<T[]>部分特殊化来统一该标准,该特殊部分将delete[]使用指针而不是对指针进行delete赋值(使用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_ptr是活着的lock,这将shared_ptr如果资源仍然存在,则返回非空值。如果要测试资源是否已耗尽,请使用expired。两者听起来很相似,但面对并发执行却有很大不同,因为它们expired只能保证单个语句的返回值。看似无辜的考验

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

是潜在的比赛条件。


1
在没有所有权的情况下,您可能应该首选对指针的引用,除非在不需要引用的情况下不需要所有权可重置性,即使这样,您可能仍要考虑将原始对象重写为a shared_ptr,将非所有权指针重写为一个weak_ptr...
DavidRodríguez-dribeas 2012年

2
我不是指引用指针,而是引用而不是指针。如果没有所有权,除非您需要可重置性(或可空性,但无法重置的可空性将受到很大限制),则可以首先使用普通引用而不是指针。
大卫·罗德里格斯(DavidRodríguez)-dribeas 2012年

1
@大卫:啊,我明白了。:)是的,引用并不是很糟糕,在这种情况下,我个人也更喜欢它们。我将其添加。
Xeo 2012年

1
@Xeo:shared_array<T>是另一种shared_ptr<T[]>shared_ptr<vector<T>>:它无法生长。
R. Martinho Fernandes

1
@GregroyCurrie:那是...我写的到底是什么?我说这是潜在比赛条件的一个例子。
Xeo

127

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

请注意,软件系统中的所有权与所有权是分开的,正如我们在软件外部所认为的那样。例如,一个人可能“拥有”自己的房屋,但这并不一定意味着某个Person对象可以控制该对象的寿命House。将这些现实世界的概念与软件概念相结合,是一种使自己陷入困境的可靠方法。


如果您拥有该对象的唯一所有权,请使用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...
};

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

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

在这里,列表节点拥有其下一个和上一个列表,因此我们定义一个方向,并shared_ptr用于下一个和上一个weak_ptr打破循环。


3
对于二叉树的示例,有人建议将其shared_ptr<BinaryTree>用于子级和weak_ptr<BinaryTree>父级关系。
DavidRodríguez-dribeas 2012年

@DavidRodríguez-dribeas:这取决于Tree是否具有值语义。如果即使源树被破坏,人们也将在外部引用您的树,那么可以,共享/弱指针组合将是最佳选择。
彼得·亚历山大

如果对象拥有您并保证存在,那么为什么不引用。
马丁·约克

1
如果使用引用,则永远不能更改父级,这可能会也可能不会妨碍设计。为了平衡树木,这将成为障碍。
Mooing Duck 2012年

3
+1,但您应该在第一行添加“所有权”的定义。我经常发现自己必须清楚地指出,这与对象的生死有关,而不是所有权的更具体领域的含义。
克莱姆(Klaim)2012年

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*和的功能operator->。无论如何,您都必须检查自己。shared_ptr尽管可以说这是通用优势,并且没有Boost依赖关系,但最终unique_ptr还是shared_ptr使赢家再次出现,这使得用户友好度略有降低。

范围指针:与无关unique_ptr,就像auto_ptr

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

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


另外,还有用于迭代的原始指针。对于输出参数缓冲区,缓冲区由调用方拥有。
Ben Voigt 2012年

嗯,按照我的理解,都是协变收益和无所有权的情况。如果您指的是联合而不是交点,则重写可能会很好。我还要说迭代也值得一提。
Ben Voigt 2012年

2
std::unique_ptr<T[]>提供operator[]代替operator*operator->。的确,您仍然需要对自己进行边界检查。
Xeo 2012年

8

何时使用的情况unique_ptr

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

何时使用的情况shared_ptr

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

何时使用的情况weak_ptr

  • 用作一般参考的大型地图(例如,所有打开的插座的地图)

随时编辑和添加更多


当您提出方案时,我实际上更喜欢您的答案。
尼古拉斯·汉弗莱
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.