22-6 std::shared_ptr
与旨在单独拥有和管理资源的 std::unique_ptr 不同,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 }; // 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对象,并设置一个名为ptr1的std::shared_ptr来管理它。在嵌套代码块内,我们使用复制构造函数创建了第二个std::shared_ptr(ptr2),它指向同一个Resource。当ptr2作用域结束时,Resource不会被释放,因为ptr1仍然指向该Resource。当ptr1作用域结束时,它检测到已无std::shared_ptr管理该Resource,因此释放了内存。
请注意:我们是从第一个共享指针创建了第二个共享指针。这点至关重要。请看以下类似程序:
#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
该程序输出:

然后程序崩溃(至少在作者的机器上如此)。
区别在于我们独立创建了两个 std::shared_ptr。因此,尽管它们都指向同一个资源,彼此并不知情。当 ptr2 作用域结束时,它认为自己是资源的唯一所有者,并释放了该资源。当ptr1随后作用域结束时,它也会产生同样的误判,再次尝试删除该Resource。此时便会引发严重错误。
所幸此问题易于规避:若需多个std::shared_ptr指向同一资源,请复制现有std::shared_ptr实例。
最佳实践:
当需要多个 std::shared_ptr 指向同一资源时,请始终复制现有 std::shared_ptr。
与 std::unique_ptr 类似,std::shared_ptr 可能为空指针,因此使用前务必检查其有效性。
std::make_shared
正如 C++14 中可使用 std::make_unique() 创建 std::unique_ptr,std::make_shared() 同样可(且应)用于创建 std::shared_ptr。该函数在 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 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::shared_ptr)。然而,使用 std::make_shared() 还能提升性能表现。其原理在于 std::shared_ptr 通过追踪指向特定资源的指针数量来实现内存管理。
深入解析 std::shared_ptr
与内部仅使用单个指针的 std::unique_ptr 不同,std::shared_ptr 内部使用两个指针。其中一个指针指向被管理的资源,另一个则指向“控制块”——这个动态分配的对象负责追踪多项信息,包括有多少个 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 共同管理该资源。
共享指针可由唯一指针创建
通过接受 std::unique_ptr 右值的特殊构造函数,可将 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++17 及更早版本中,std::shared_ptr 并未提供对数组管理的完整支持,因此不应用于管理 C 风格数组。自 C++20 起,std::shared_ptr 已正式支持数组管理。
结论
std::shared_ptr 专为需要多个智能指针协同管理同一资源的情形而设计。当最后一个管理该资源的 std::shared_ptr 被销毁时,该资源将被释放。

浙公网安备 33010602011771号