跳到主要内容
版本:1.0

shared_ptr

std::shared_ptr 是 C++11 引入的智能指针类型,用于管理动态分配的对象。它的核心特点是:共享所有权,允许多个 shared_ptr 实例指向同一个对象,并通过引用计数机制自动管理资源释放。

当最后一个 shared_ptr 释放该对象时,资源会自动销毁。相比手写裸指针计数逻辑,shared_ptr 更安全且可维护。

头文件与基本特征

使用 shared_ptr 时,通常包含:

#include <memory>

主要特征:

特征说明
所有权模型共享所有权
引用计数通过控制块记录拥有者数量
拷贝语义允许拷贝,计数递增
移动语义支持移动,计数通常不变
自动释放最后一个拥有者销毁时释放对象

基本用法

推荐使用 std::make_shared 创建对象:

#include <iostream>
#include <memory>

int main() {
std::shared_ptr<int> p = std::make_shared<int>(42);
std::cout << *p << '\n';
return 0;
}

也可以直接传裸指针,但更推荐 make_shared

std::shared_ptr<int> p(new int(42));

交互演示(MDX + React)

下面这个面板用于演示 copymoveresetuse_count() 以及 weak_ptr::lock() 的典型行为:

std::shared_ptr 引用计数演示

点击操作观察 use_count 变化,以及 weak_ptr::lock() 的行为。

MDX + React
ptrAResource#1
ptrBnullptr
ptrCnullptr
use_count()1
对象状态alive
weak_ptrobserving

共享与移动

释放控制

弱引用观察

最近操作

  • 初始化: ptrA 指向 Resource#1, weakPtr 观察中

拷贝与引用计数

shared_ptr 可以拷贝,共享同一个对象:

#include <iostream>
#include <memory>

int main() {
std::shared_ptr<int> p1 = std::make_shared<int>(100);
std::shared_ptr<int> p2 = p1;

std::cout << p1.use_count() << '\n'; // 2
std::cout << p2.use_count() << '\n'; // 2
return 0;
}

只要还有任意一个 shared_ptr 持有对象,资源就不会释放。

移动语义

shared_ptr 也支持移动:

#include <memory>

int main() {
std::shared_ptr<int> p1 = std::make_shared<int>(7);
std::shared_ptr<int> p2 = std::move(p1);

// p1 变空,p2 持有对象
return 0;
}

移动会转移这个指针变量的持有关系,通常不会增加引用计数。

常用成员函数

函数作用
use_count()返回当前共享拥有者数量
unique()是否为唯一拥有者(C++20 已移除,C++11 可用)
get()获取裸指针(不转移所有权)
reset()释放当前持有对象
reset(ptr)释放旧对象并接管新对象
swap(other)与另一个 shared_ptr 交换
operator* / operator->像普通指针一样访问对象
operator bool判断是否为空

use_count()

std::shared_ptr<int> p1 = std::make_shared<int>(10);
std::shared_ptr<int> p2 = p1;
int count = p1.use_count(); // 2

use_count() 适合调试和理解所有权关系,不建议把它作为复杂并发逻辑判断依据。

reset()

std::shared_ptr<int> p = std::make_shared<int>(5);
p.reset(); // 放弃所有权

如果这是最后一个拥有者,对象会被销毁。

函数参数与返回值

参数传递策略

  1. 只读使用且不延长生命周期:const T& 或裸引用/指针更轻量。
  2. 函数内需要共享并延长对象生命周期:按值接收 std::shared_ptr<T>
  3. 只观察,不拥有:std::weak_ptr<T>

按值接收示例:

#include <memory>

void storeLater(std::shared_ptr<int> p) {
// 复制 shared_ptr,延长对象生命周期
}

int main() {
auto p = std::make_shared<int>(3);
storeLater(p);
return 0;
}

作为返回值

#include <memory>

std::shared_ptr<int> createShared() {
return std::make_shared<int>(99);
}

与 weak_ptr 配合

weak_ptr 不增加引用计数,常用于“观察但不拥有”。

#include <memory>

int main() {
std::shared_ptr<int> sp = std::make_shared<int>(10);
std::weak_ptr<int> wp = sp;

if (std::shared_ptr<int> locked = wp.lock()) {
// 对象还活着
}

sp.reset();
if (wp.expired()) {
// 对象已销毁
}

return 0;
}

循环引用问题

shared_ptr 最大的坑之一是循环引用:

struct B;

struct A {
std::shared_ptr<B> b;
};

struct B {
std::shared_ptr<A> a;
};

即使外部不再持有 AB,它们内部彼此引用也会让计数不归零,导致内存泄漏。典型解决办法是把一侧改为 std::weak_ptr

使用注意

  1. 优先 make_shared,减少额外分配和异常路径复杂度。
  2. 不要从同一个裸指针构造多个独立 shared_ptr,否则会重复释放。
  3. 避免不必要的拷贝,能传引用时尽量传引用。
  4. 关注循环引用,必要时引入 weak_ptr
  5. use_count() 适合诊断,不要滥用作业务逻辑判断。

小结

std::shared_ptr 适合“多个对象共同管理同一资源”的场景。它通过引用计数简化生命周期管理,但也带来控制块开销和循环引用风险。掌握 make_shared、拷贝共享、resetuse_count 以及 weak_ptr 协作后,就能在大多数工程场景里安全使用共享所有权模型。