std::weak_ptr 是 C++ 中与 std::shared_ptr 配套使用的智能指针,用于观察共享资源而不影响其生命周期。以下是其核心特性、使用场景及详细示例:
1. 核心特性
| 特性 | 说明 |
|---|---|
| 不持有所有权 | 不增加引用计数,仅作为观察者存在 |
依赖 shared_ptr | 必须通过 shared_ptr 构造,不能直接管理资源 |
| 安全访问资源 | 通过 lock() 返回临时 shared_ptr,避免访问已释放资源 |
| 解决循环引用 | 打破 shared_ptr 的循环依赖,防止内存泄漏 |
2. 基础用法
构造与初始化
auto shared = std::make_shared<int>(42);
std::weak_ptr<int> weak(shared); // 通过 shared_ptr 构造 weak_ptr
安全访问资源
if (auto temp_shared = weak.lock()) { // 尝试提升为 shared_ptr
std::cout << *temp_shared; // 资源存在时安全访问
} else {
std::cout << "资源已释放";
}
检查有效性
// 方法 1:检查提升后的 shared_ptr 是否为空
bool valid = !weak.expired(); // 不保证准确性(可能瞬间失效)
// 方法 2(推荐):直接尝试 lock()
if (auto sp = weak.lock()) {
// 操作 sp
}
3. 典型应用场景
场景 1:打破循环引用
// 错误示例:父节点和子节点互相持有 shared_ptr
class Node {
public:
std::shared_ptr<Node> child; // 导致循环引用,无法释放
// 修正方案:
std::weak_ptr<Node> parent; // 使用 weak_ptr 断开循环
};
场景 2:缓存临时访问
class Cache {
std::unordered_map<int, std::weak_ptr<Resource>> cache;
public:
std::shared_ptr<Resource> get(int key) {
auto it = cache.find(key);
if (it != cache.end()) {
return it->second.lock(); // 资源存在则复用,否则新建
}
auto new_res = std::make_shared<Resource>(key);
cache[key] = new_res; // 存储 weak_ptr
return new_res;
}
};
场景 3:观察者模式
class Subject {
std::vector<std::weak_ptr<Observer>> observers;
public:
void notify() {
for (auto& weak_obs : observers) {
if (auto obs = weak_obs.lock()) {
obs->update(); // 仅通知存活的观察者
}
}
}
};
4. 底层原理
控制块结构
+-------------------+
| Control Block |
| +---------------+ |
| | 强引用计数 | |
| | (shared_ptr) | |
| +---------------+ |
| +---------------+ |
| | 弱引用计数 | |
| | (weak_ptr) | |
| +---------------+ |
+-------------------+
|
v
Object
- 强引用计数归零时:销毁对象
- 弱引用计数归零时:释放控制块
5. 性能与线程安全
| 操作 | 性能影响 | 线程安全保证 |
|---|---|---|
| 构造/析构 | 无原子操作,低成本 | 无竞争条件下安全 |
lock() | 涉及原子操作,中等开销 | 线程安全(返回的 shared_ptr 需用户同步) |
expired() | 快速检查,但结果可能瞬间失效 | 需外部同步保证准确性 |
6. 常见错误与规避
错误 1:直接访问 weak_ptr 管理的资源
std::weak_ptr<Widget> weak = ...;
weak->do_something(); // 错误!weak_ptr 没有重载 -> 和 * 操作符
修正:必须通过 lock() 获取临时 shared_ptr 后访问。
错误 2:长期持有 weak_ptr
void process(std::weak_ptr<Data> weak_data) {
// 长时间运行后...
if (auto data = weak_data.lock()) { // 可能已失效
data->process();
}
}
修正:尽早 lock() 并持有返回的 shared_ptr。
错误 3:误用 expired() 作为同步检查
if (!weak.expired()) { // 不安全:检查后可能立即失效
auto sp = weak.lock(); // sp 可能为空
if (sp) sp->operate();
}
修正:原子化检查与提升操作:
if (auto sp = weak.lock()) { // 原子操作
sp->operate();
}
7. 最佳实践
-
始终通过
lock()访问资源- 避免使用
expired()单独判断有效性。 - 对返回的
shared_ptr判空后再操作。
- 避免使用
-
优先使用
make_sharedauto shared = std::make_shared<Object>(); // 合并对象和控制块内存 std::weak_ptr<Object> weak(shared);- 避免裸指针初始化导致的分离内存分配。
-
避免跨线程传递
weak_ptr的时效性假设- 即使
lock()成功,资源可能在后续代码中失效,需设计重试机制。
- 即使
-
限制生命周期
{ auto shared = std::make_shared<Resource>(); std::weak_ptr<Resource> weak(shared); // 确保 shared 存活期间使用 weak } // shared 析构后,weak 自动失效
示例:弱引用工厂
class ObjectFactory {
static std::vector<std::weak_ptr<Object>> instances;
public:
static std::shared_ptr<Object> create() {
auto obj = std::make_shared<Object>();
instances.emplace_back(obj);
return obj;
}
static void cleanup() {
// 清除所有已释放的弱引用
auto it = std::remove_if(instances.begin(), instances.end(),
[](const auto& weak) { return weak.expired(); });
instances.erase(it, instances.end());
}
};
总结
std::weak_ptr 是 shared_ptr 生态的重要补充,核心价值在于:
- 安全观察共享资源,不干预生命周期
- 解决循环引用导致的内存泄漏
- 实现缓存、观察者等模式
使用时需严格遵循 lock() 流程,避免直接操作资源指针,同时注意多线程环境下的状态一致性。正确应用可显著提升代码的健壮性和资源管理效率。

被折叠的 条评论
为什么被折叠?



