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)
下面这个面板用于演示 copy、move、reset、use_count() 以及 weak_ptr::lock() 的典型行为:
std::shared_ptr 引用计数演示
点击操作观察 use_count 变化,以及 weak_ptr::lock() 的行为。
最近操作
- 初始化: 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(); // 放弃所有权
如果这是最后一个拥有者,对象会被销毁。
函数参数与返回值
参数传递策略
- 只读使用且不延长生命周期:
const T&或裸引用/指针更轻量。 - 函数内需要共享并延长对象生命周期:按值接收
std::shared_ptr<T>。 - 只观察,不拥有:
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;
};
即使外部不再持有 A、B,它们内部彼此引用也会让计数不归零,导致内存泄漏。典型解决办法是把一侧改为 std::weak_ptr。
使用注意
- 优先
make_shared,减少额外分配和异常路径复杂度。 - 不要从同一个裸指针构造多个独立
shared_ptr,否则会重复释放。 - 避免不必要的拷贝,能传引用时尽量传引用。
- 关注循环引用,必要时引入
weak_ptr。 use_count()适合诊断,不要滥用作业务逻辑判断。
小结
std::shared_ptr 适合“多个对象共同管理同一资源”的场景。它通过引用计数简化生命周期管理,但也带来控制块开销和循环引用风险。掌握 make_shared、拷贝共享、reset、use_count 以及 weak_ptr 协作后,就能在大多数工程场景里安全使用共享所有权模型。