unique_ptr和shared_ptr之间的区别

342

智能指针boost解释 - legion
4个回答

675
这两个类都是智能指针,这意味着它们自动(在大多数情况下)在不再引用对象时释放它们所指向的对象。两者之间的区别在于每种类型的指针可以引用多少个资源。
使用 unique_ptr 时,最多只能有一个 unique_ptr 指向任何一个资源。当该 unique_ptr 被销毁时,资源会自动回收。因为任何资源只能有一个 unique_ptr,试图复制 unique_ptr 将导致编译时错误。例如,此代码是非法的:
unique_ptr<T> myPtr(new T);       // Okay
unique_ptr<T> myOtherPtr = myPtr; // Error: Can't copy unique_ptr

然而,unique_ptr可以使用新的移动语义进行移动

unique_ptr<T> myPtr(new T);                  // Okay
unique_ptr<T> myOtherPtr = std::move(myPtr); // Okay, resource now stored in myOtherPtr

同样地,你可以像这样做:
unique_ptr<T> MyFunction() {
    unique_ptr<T> myPtr(/* ... */);

    /* ... */

    return myPtr;
}

这个习语的意思是“我将一个托管资源归还给你。如果你不显式地捕获返回值,那么该资源将被清除。如果你这样做了,那么你现在拥有该资源的独占所有权。”通过这种方式,你可以把unique_ptr看作是auto_ptr的更安全、更好的替代品。
另一方面,shared_ptr允许多个指针指向同一个资源。当最后一个指向该资源的shared_ptr被销毁时,该资源将被释放。例如,下面的代码是完全合法的:
shared_ptr<T> myPtr(new T);       // Okay
shared_ptr<T> myOtherPtr = myPtr; // Sure!  Now have two pointers to the resource.

在内部,shared_ptr使用引用计数来跟踪有多少指针引用资源,因此您需要小心不要引入任何引用循环。

简而言之:

  1. 当您想要一个单一指向对象的指针,并且当该单一指针被销毁时将回收该对象时,请使用unique_ptr
  2. 当您想要多个指针指向同一资源时,请使用shared_ptr

35
unique_ptr 在处理删除器方面可能会有一些小 bug。只要使用 make_shared 创建,shared_ptr 就总是会做“正确的事情”。但是如果你创建了一个 unique_ptr<Derived>,然后将其转换为 unique_ptr<Base>,并且 Derived 是虚拟的而 Base 不是,则指针将通过错误类型进行删除,并且可能会导致未定义的行为。可以使用适当的删除器类型在 unique_ptr<T, DeleterType> 中进行修复,但默认情况下使用较高风险的版本,因为它更加高效。 - Aaron McDaid
14
默认情况下,unique_ptr 的行为类似于一个你不会忘记delete的原始指针:如果你忘记使析构函数为虚函数,那就是你的责任。 - curiousguy
4
我更喜欢使用make_unique而不是new。它更加简洁、安全和高效。 - SubMachine
2
@templatetypedef,您可以举个例子说明如何将MyFunction()的返回值捕获到调用者范围内的另一个指针中吗? - Vassilis
2
这是一个非常好的答案。 - tae ha

99

unique_ptr是轻量级的智能指针,如果你有一个动态对象,只有一个消费者(因此“unique”)负责, 例如需要维护一些动态分配的对象的包装类,那么它就是首选。 unique_ptr几乎没有开销。它不可复制,但可移动。其类型为template <typename D, typename Deleter> class unique_ptr;,所以它依赖于两个模板参数。

unique_ptr也是旧版C++中auto_ptr想要成为的东西,但由于该语言的限制而无法实现。

另一方面,shared_ptr则是一个非常不同的概念。明显的区别是,您可以让多个消费者共享动态对象的责任(因此“shared”),并且仅在所有共享指针都消失时才会销毁对象。此外,您可以拥有观察弱指针,这些指针将智能地被通知,如果它们跟随的共享指针已经消失。

内部,shared_ptr具有更多的功能:有一个引用计数,它会被原子方式更新以允许在并发代码中使用。此外,还有大量的分配操作,一个用于内部记账“引用控制块”,另一个(通常)用于实际成员对象。

但是还有另一个重要的区别:共享指针的类型始终为template <typename T> class shared_ptr;,尽管您可以使用自定义删除器和自定义分配器进行初始化。删除器和分配器使用类型擦除和虚函数调度进行跟踪,这增加了类的内部负担,但具有巨大的优势,即不同类型的T的共享指针都是兼容的,无论删除和分配的细节如何。因此,它们真正表达了“对T的共享责任”的概念,而不会给消费者带来负担!

无论shared_ptr还是unique_ptr都被设计为按值传递(对于唯一指针,显然需要可移动性)。由于它们的强大功能,两者都不应让您担心开销,但如果您有选择,则应首选unique_ptr,只有在确实需要共享责任时才使用shared_ptr


4
"旨在按值传递设计" - Ajay
4
因为我对shared_ptr的引用计数器实际上可以在哪里产生困惑(毕竟,多个shared_ptrs和单个共享资源意味着只有一个共享引用计数器,对吧?所以计数器不能包含在shared_ptr中)。这是我看到的第一个解答我的问题的shared_ptr解释;会分配一个单独的引用计数器。它可能在计数0->1时分配一次,在计数1->0时释放,就像受控制的资源一样。 - Bill Forster
1
如果你无法复制它,如何通过值传递它? - splinter123
12
通过移动它! - Kerrek SB
2
@Farzan:是的,确切如此。你可以使用临时对象调用函数(根据定义,无法访问该临时对象),或者你需要传递像std::move(x)这样的参数,这意味着x被移动。 - Kerrek SB
显示剩余4条评论

25

unique_ptr
是一种独占对象的智能指针。

shared_ptr
是一种用于共享所有权的智能指针,它支持拷贝和移动。多个智能指针实例可以共同拥有同一个资源。只要最后一个拥有该资源的智能指针超出范围,该资源就会被释放。


15

当用unique_ptr包装一个指针时,你不能拥有多个unique_ptr的副本。而shared_ptr则持有一个引用计数器来统计所存储指针的副本数量。每次复制shared_ptr时,该计数器会增加一次;每次销毁shared_ptr时,该计数器会减少一次。当这个计数器减少到0时,存储的对象就会被销毁。


1
计数器是一种实现细节。重要的是,“共享指针”家族中彼此复制的成员能够确定何时销毁家族的最后一个成员。 - curiousguy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接