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

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


当前回答

决定使用哪种智能指针是所有权的问题。当涉及到资源管理时,如果对象A控制对象B的生命周期,则对象A拥有对象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;
};

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

其他回答

除非需要引用计数,否则一直使用unique_ptr<T>,在这种情况下使用shared_ptr<T>(在非常罕见的情况下,使用weak_ptr<T>以防止引用循环)。在几乎所有情况下,可转让的唯一所有权都是可以的。

原始指针:只有当你需要协变返回时才有用,非拥有的指针可能会发生。除此之外,它们并没有太大用处。

Array pointers: unique_ptr has a specialization for T[] which automatically calls delete[] on the result, so you can safely do unique_ptr<int[]> p(new int[42]); for example. shared_ptr you'd still need a custom deleter, but you wouldn't need a specialized shared or unique array pointer. Of course, such things are usually best replaced by std::vector anyway. Unfortunately shared_ptr does not provide an array access function, so you'd still have to manually call get(), but unique_ptr<T[]> provides operator[] instead of operator* and operator->. In any case, you have to bounds check yourself. This makes shared_ptr slightly less user-friendly, although arguably the generic advantage and no Boost dependency makes unique_ptr and shared_ptr the winners again.

作用域指针:通过unique_ptr使其无关紧要,就像auto_ptr一样。

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

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

决定使用哪种智能指针是所有权的问题。当涉及到资源管理时,如果对象A控制对象B的生命周期,则对象A拥有对象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;
};

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

共享所有权: 所采用的shared_ptr和weak_ptr标准与Boost标准几乎相同。当你需要共享一个资源,不知道谁会是最后一个活下来的时候,可以使用它们。使用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[]>部分特化来统一它,该特化将删除[]指针,而不是删除它(使用default_deleter)。std::unique_ptr<T[]>也提供了operator[]而不是operator*和operator->。

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

类模板auto_ptr已弃用。[注意:类模板unique_ptr(20.7.1)提供了一个更好的解决方案。-结束注释]

没有所有权: 当您知道资源将比引用对象/作用域更长寿时,使用哑指针(原始指针)或对资源的非所有引用的引用。当您需要可空性或可重置性时,请优先使用引用和原始指针。

If you want a non-owning reference to a resource, but you don't know if the resource will outlive the object that references it, pack the resource in a shared_ptr and use a weak_ptr - you can test if the parent shared_ptr is alive with lock, which will return a shared_ptr that is non-null if the resource still exists. If want to test whether the resource is dead, use expired. The two may sound similar, but are very different in the face of concurrent execution, as expired only guarantees its return value for that single statement. A seemingly innocent test like

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

是潜在的竞争条件。

何时使用unique_ptr的情况:

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

何时使用shared_ptr的情况:

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

何时使用weak_ptr的情况:

充当通用引用的大映射(例如,所有打开套接字的映射)

请随意编辑和添加更多内容