Skip to content

15.6 std::shared_ptr

By Alex on March 16th, 2017 | last modified by Alex on January 23rd, 2020

翻译by dashjay 2020.07.18

不像 std::unique_ptr 那样,仅仅被设计单独拥有和管理一份资源,std::shared_ptr 是为了解决你需要创建很多智能指针共用一个资源的情况。

这意味着,同时许多 std::shared_ptr 指向同一份资源是OK的。在内部,std::shared_ptr 保持跟踪正在分享同一份资源的 std::shared_ptr 数量。当只要有一个 std::shared_ptr 指向资源,这个资源就不会被释放,即使一些 std::shared_ptr 被销毁。当最后一个管理着资源的 std::shared_ptr 离开作用域时(或者被重新赋值指向其他资源)原来管理的资源就会被销毁。

std::unique_ptr 一样,std_shared_ptr 头部中。

#include <iostream>
#include <memory> // for std::shared_ptr

class Resource
{
public:
 Resource() { std::cout << "Resource acquired\n"; }
 ~Resource() { std::cout << "Resource destroyed\n"; }
};

int main()
{
 // allocate a Resource object and have it owned by std::shared_ptr
 Resource *res = new Resource;
 std::shared_ptr<Resource> ptr1(res);
 {
  std::shared_ptr<Resource> ptr2(ptr1); // use copy initialization to make another std::shared_ptr pointing to the same thing

  std::cout << "Killing one shared pointer\n";
 } // ptr2 goes out of scope here, but nothing happens

 std::cout << "Killing another shared pointer\n";

 return 0;
} // ptr1 goes out of scope here, and the allocated Resource is destroyed

输出

Resource acquired
Killing one shared pointer
Killing another shared pointer
Resource destroyed

在上方的代码中,我们创建了一个动态资源对象,并且设置了一个名字为 ptr1std::shared_ptr 来管理它。在这个嵌套的语句块中,我们用拷贝初始化(std::shared_ptr 中允许的操作,因为资源可以被共享 )来创建第二个 std::shared_ptr(ptr2)指向同一份资源。当ptr1离开作用域时,ptr1注意到这里已经没有 std::shared_ptr管理这份资源了,因此我们释放该资源。

注意我们创建了第二个智能指针从第一个智能指针 (使用拷贝初始化)。这很重要,思考如下代码。

#include <iostream>
#include <memory> // for std::shared_ptr

class Resource
{
public:
 Resource() { std::cout << "Resource acquired\n"; }
 ~Resource() { std::cout << "Resource destroyed\n"; }
};

int main()
{
 Resource *res = new Resource;
 std::shared_ptr<Resource> ptr1(res);
 {
  std::shared_ptr<Resource> ptr2(res); // create ptr2 directly from res (instead of ptr1)

  std::cout << "Killing one shared pointer\n";
 } // ptr2 goes out of scope here, and the allocated Resource is destroyed

 std::cout << "Killing another shared pointer\n";

 return 0;
} // ptr1 goes out of scope here, and the allocated Resource is destroyed again

输出

Resource acquired
Killing one shared pointer
Resource destroyed
Killing another shared pointer
Resource destroyed

然后紧接着崩溃(至少在作者的机器上)。

这里不同的是我们创建了两个相互独立的 std::shared_Ptr。结果,即使他们都指向 同一份资源,但是他们都没意识到对方的存在。当 ptr2 离开作用域时,他 认为 他是资源唯一的拥有者,所以释放了它。当 ptr1 之后离开作用域时,它思考的着同样的问题,并且尝试再次删除它。糟糕的事情发生了。

幸运的是,当你需要多个共享指针指向同一份资源时,这是非常容易使用 拷贝赋值 或者 拷贝初始化 来避免的。

规定:当你需要不止一个 std::shared_ptr 指向同一份资源,请总是从已存在的 std::shared_ptr 创建拷贝。

std::make_shared

就像在C++14中 std::make_unique() 可以被用来创建 std::unique_ptr 一样,std::make_shared() 可以(并且应该)被用来创建一个 std::shared_ptrstd::make_shared() 在C++11中就可用。

有些例子,使用了 std::make_shared():

#include <iostream>
#include <memory> // for std::shared_ptr

class Resource
{
public:
 Resource() { std::cout << "Resource acquired\n"; }
 ~Resource() { std::cout << "Resource destroyed\n"; }
};

int main()
{
 // allocate a Resource object and have it owned by std::shared_ptr
 auto ptr1 = std::make_shared<Resource>();
 {
  auto ptr2 = ptr1; // create ptr2 using copy initialization of ptr1

  std::cout << "Killing one shared pointer\n";
 } // ptr2 goes out of scope here, but nothing happens

 std::cout << "Killing another shared pointer\n";

 return 0;
} // ptr1 goes out of scope here, and the allocated Resource is destroyed

使用 std::make_shared() 的原因和 std::make_unique()一样 ———— std::make_shared() 更简单更安全(使用这个方法不可能直接创建两个相互独立的 std::shared_ptr 指向同一块资源),而且,比起不使用它,std::make_shared() 有更加高性能。其原因在于 std::shared ptr 跟踪指向给定资源的指针数量。

深挖 std::shared_ptr

不像内部仅仅使用一个指针的std::unique_ptr 那样,std::shared_ptr 内部有两个指针:一个指针指向被管理的资源,另一个指针在“控制块(control block)”,是一个动态分配的对象,会跟踪很多东西,包括有多少个 std::shared_ptr 指向资源。

当一个 std::shared_ptr 被使用构造函数单独创建的时候,管理对象(传入的资源)和控制块(构造器创建)的内存就被单独分配了。然而,当使用 std::make_shared() 时,这可以被优化成一个单独内存分配,有更好的性能。

这也解释了为什么单独创建两个 std::shared_ptr 会遇到问题。每个 std::shared_ptr将会有一个指针指向资源,然而每个 std::shared_ptr 独立分配它自己的控制块,这意味着这是持有资源的唯一的指针。因此,当 std::shared_ptr 离开作用域后,会释放资源,并没有意识到还有另一个 std::shared_ptr 仍然在管理资源。

然而,当一个 std::shared_ptr 被使用拷贝构造复制的时候,控制块中的数据同样被更新成合适的值,表明有另一个 std::shared_ptr 共同管理这个资源。

Shared pointers 可以从 Unique pointers 创建

一个 std::unique_ptr 可以被转化成一个 std::shared_ptr 通过一个特殊的构造函数接收右值。std::unique_ptr 持有的内容将会移动给 std::shared_ptr

然而,std::shared_ptr 却不能安全的转化成 std::unique_ptr。这意味着如果你正在创建一个函数,该函数返回一个智能指针,你最好返回一个 std::unique_ptr 并且赋值它给一个 std::shared_ptr 如果合适的话。

使用 std::shared_ptr 的危险之处

std::shared_ptr 有一些和 std::unique_ptr 同样的挑战,如果 std::shared_ptr 没有被合适的处理(可能是因为它被动态分配并且从未删除,或者它作为对象的一部分,被动态分配并从未删除),紧接着它管理的资源也将不会被释放。使用 std::unique_ptr,你仅需要去关注智能指针是否被合适的处理。如果使用 std::shared_ptr ,你不得不担心他们全部。如果任何一个 std::shared_ptr 管理资源没有被合适的清理,资源将不会被释放。

std::shared_ptr 和数组

在 C++14 或更早,std::shared_ptr 没办法支持管理数组,并且不应该使用C类型数组。在C++17中, std::shared_ptr 已经支持数组了。然而,在C++17中,std::make_shared 仍然缺乏合适的针对数组的支持,并且不应该被用来创建数组,这会在C++20中被解决。

结论

std::shared_ptr 被设计用于你需要多个智能指针共同管理同样的资源。资源警徽被释放,当最后一个 std::shared_ptr 管理的资源被销毁时。