关于C++:我什么时候使用哪种指针?

Which kind of pointer do I use when?

好的,所以我最后一次写C++为生,EDOCX1,0,所有的STD LIB都是可用的,EDCOX1,1是愤怒的。我从未真正研究过Boost提供的其他智能指针类型。我知道C++ 11现在提供了一些类型的Boost,但不是全部。

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


共享所有权:采用的shared_ptrweak_ptr标准与它们的增强型标准基本相同。当你需要共享一个资源,不知道哪一个是最后一个活着的时候,就使用它们。使用weak_ptr观察共享资源,不影响其生命周期,不破坏循环。使用shared_ptr的周期通常不应该发生-两个资源不能相互拥有。

请注意,Boost还提供了shared_array,这可能是shared_ptr const>的一个合适的替代方案。

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

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

再次注意,Boost有一个数组版本:scoped_array,标准统一要求std::unique_ptr部分专门化,将delete[]作为指针,而不是delete作为指针(使用default_deleter)。std::unique_ptr也提供operator[]而不是operator*operator->

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

The class template auto_ptr is deprecated. [ Note: The class template unique_ptr (20.7.1) provides a better solution. —end note ]

无所有权:当您知道资源将比引用对象/作用域长时,请使用哑指针(原始指针)或引用来表示对资源的非拥有引用。当需要可空性或可重设性时,首选引用并使用原始指针。

如果您想要一个对资源的非拥有引用,但您不知道该资源是否比引用它的对象寿命长,请将该资源打包到shared_ptr中,并使用weak_ptr—您可以测试父级shared_ptr是否与lock共存,如果该资源仍然存在,则返回一个非空的shared_ptr。如果要测试资源是否已死亡,请使用expired。这两个听起来可能很相似,但在同时执行时却截然不同,因为expired只保证它对单个语句的返回值。一个看似无辜的测试

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

是一种潜在的竞争条件。


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

请注意,软件系统中的所有权与所有权是分开的,正如我们在软件之外所想的那样。例如,一个人可能"拥有"自己的家,但这并不一定意味着一个Person对象在House对象的生命周期内拥有控制权。将这些现实世界的概念与软件概念相结合,无疑是一种让自己陷入困境的方法。

如果您拥有该物品的唯一所有权,请使用std::unique_ptr

如果您共享了对象的所有权…-如果没有所有权循环,则使用std::shared_ptr。-如果有循环,定义一个"方向",在一个方向上使用std::shared_ptr,在另一个方向上使用std::weak_ptr

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

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

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

成本:

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

实例:

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

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

1
2
3
4
5
struct ListNode
{
    std::shared_ptr<ListNode> m_next;
    std::weak_ptr<ListNode> m_prev;
};

在这里,list节点拥有其下一个和上一个列表,因此我们定义了一个方向,并使用shared_ptr表示下一个,使用weak_ptr表示prev来中断循环。


一直使用unique_ptr,除非需要参考计数,在这种情况下,使用shared_ptr(对于非常罕见的情况,使用weak_ptr来防止参考循环)。几乎每种情况下,可转让的唯一所有权都是好的。

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

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

作用域指针:被unique_ptr置为无关,就像auto_ptr一样。

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

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


何时使用unique_ptr的情况:

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

何时使用shared_ptr的情况:

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

何时使用weak_ptr的情况:

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

随意编辑和添加更多内容