016 C++的weak_ptr
作者Lou Xiao, deepseek创建时间2025-04-02 14:16:16更新时间2025-04-02 14:16:16
1. 概述
std::weak_ptr
是 C++11 引入的智能指针,用于解决 std::shared_ptr
的循环引用问题。它是一种"弱引用"指针,不增加引用计数,不影响对象的生命周期。
2. 基本特性
2.1 主要特点
- 不拥有对象的所有权
- 不增加引用计数
- 可以从
std::shared_ptr
创建 - 可以检测所指向的对象是否已被释放
2.2 与 shared_ptr
的关系
weak_ptr
是shared_ptr
的配套工具- 必须与
shared_ptr
配合使用 - 用于观察
shared_ptr
管理的资源而不影响其生命周期
3. 核心操作
3.1 构造与赋值
1.双击鼠标左键复制此行;2.单击复制所有代码。
1
// 默认构造
2
std::weak_ptr<T> wp1;
3
4
// 从 shared_ptr 构造
5
std::shared_ptr<T> sp(new T);
6
std::weak_ptr<T> wp2(sp);
7
8
// 拷贝构造
9
std::weak_ptr<T> wp3(wp2);
10
11
// 赋值操作
12
wp1 = sp;
13
wp1 = wp2;
3.2 关键方法
1.双击鼠标左键复制此行;2.单击复制所有代码。
1
// 获取关联的 shared_ptr
2
std::shared_ptr<T> locked = wp.lock();
3
4
// 检查引用计数
5
long use_count = wp.use_count(); // 可能已过期
6
7
// 检查是否为空
8
bool expired = wp.expired(); // use_count == 0
3.3 重置操作
1.双击鼠标左键复制此行;2.单击复制所有代码。
1
wp.reset(); // 释放观察权,不再指向任何对象
4. 典型应用场景
4.1 解决循环引用
1.双击鼠标左键复制此行;2.单击复制所有代码。
1
struct Node {
2
std::shared_ptr<Node> next;
3
std::weak_ptr<Node> prev; // 使用 weak_ptr 打破循环
4
};
4.2 缓存实现
1.双击鼠标左键复制此行;2.单击复制所有代码。
1
class Cache {
2
std::unordered_map<Key, std::weak_ptr<Value>> cache;
3
public:
4
std::shared_ptr<Value> get(Key key) {
5
auto it = cache.find(key);
6
if (it != cache.end()) {
7
return it->second.lock(); // 转为 shared_ptr 或 nullptr
8
}
9
return nullptr;
10
}
11
};
4.3 观察者模式
1.双击鼠标左键复制此行;2.单击复制所有代码。
1
class Subject {
2
std::vector<std::weak_ptr<Observer>> observers;
3
public:
4
void notify() {
5
for (auto& wp : observers) {
6
if (auto sp = wp.lock()) {
7
sp->update();
8
}
9
}
10
}
11
};
5. 注意事项
5.1 线程安全
weak_ptr
本身的操作是原子的- 但
lock()
和后续使用不是原子的组合操作 - 多线程环境下需要额外同步
5.2 性能考虑
weak_ptr
比shared_ptr
构造和拷贝成本略高lock()
操作需要原子操作,有一定开销
5.3 使用限制
- 不能直接解引用 (
operator*
和operator->
) - 必须通过
lock()
转为shared_ptr
后才能访问对象
6. 实现原理
6.1 控制块结构
1.双击鼠标左键复制此行;2.单击复制所有代码。
1
+---------------------+
2
| Control Block |
3
| +-----------------+ |
4
| | strong refcount | |
5
| +-----------------+ |
6
| | weak refcount | |
7
| +-----------------+ |
8
| | Deleter | |
9
| +-----------------+ |
10
| | Allocator | |
11
| +-----------------+ |
12
+---------------------+
6.2 生命周期管理
- 当最后一个
shared_ptr
销毁时,对象被销毁 - 控制块会保留直到最后一个
weak_ptr
销毁
7. 最佳实践
- 优先使用
weak_ptr
作为观察者 - 及时检查
expired()
或lock()
结果 - 避免长期持有
weak_ptr
导致控制块泄漏 - 在性能敏感场景慎用
lock()
8. 示例代码
1.双击鼠标左键复制此行;2.单击复制所有代码。
1
#include <memory>
2
#include <iostream>
3
4
class Resource {
5
public:
6
Resource() { std::cout << "Resource acquired\n"; }
7
~Resource() { std::cout << "Resource released\n"; }
8
void use() { std::cout << "Resource used\n"; }
9
};
10
11
int main() {
12
// 创建 shared_ptr
13
std::shared_ptr<Resource> sp = std::make_shared<Resource>();
14
15
// 创建 weak_ptr
16
std::weak_ptr<Resource> wp = sp;
17
18
// 使用资源
19
if (auto locked = wp.lock()) {
20
locked->use();
21
std::cout << "Use count: " << wp.use_count() << "\n";
22
}
23
24
// 释放 shared_ptr
25
sp.reset();
26
27
// 检查资源是否还存在
28
if (wp.expired()) {
29
std::cout << "Resource has been released\n";
30
}
31
32
return 0;
33
}
9. 总结
std::weak_ptr
是 C++智能指针体系中的重要组成部分,主要用于:
- 打破 shared_ptr
的循环引用
- 实现非拥有式观察
- 构建缓存等需要弱引用的场景
正确使用 weak_ptr
可以避免内存泄漏同时保持程序的灵活性。
附录: std::weak_ptr
的常用 API 表格
名称 | 用途 | 原型 | 代码示例 | 说明 |
---|---|---|---|---|
构造函数 | 创建 weak_ptr 对象 | weak_ptr() noexcept; weak_ptr(const weak_ptr& x) noexcept; weak_ptr(weak_ptr&& x) noexcept; | cpp<br>std::weak_ptr<int> w1; // 空指针<br>std::shared_ptr<int> s = std::make_shared<int>(42);<br>std::weak_ptr<int> w2(s); // 从 shared_ptr 构造<br> | 默认构造为空;支持拷贝/移动构造(不增加引用计数)。 |
析构函数 | 释放 weak_ptr 资源 | ~weak_ptr(); | (隐式调用) | 析构时不影响关联的 shared_ptr 引用计数。 |
operator= | 赋值操作 | weak_ptr& operator=(const weak_ptr& x) noexcept; weak_ptr& operator=(weak_ptr&& x) noexcept; | cpp<br>std::weak_ptr<int> w3 = w2; // 拷贝赋值<br>std::weak_ptr<int> w4 = std::move(w3); // 移动赋值<br> | 赋值操作不增加引用计数,可能释放原有资源。 |
swap | 交换两个 weak_ptr 对象 | void swap(weak_ptr& x) noexcept; | cpp<br>w1.swap(w2); // 交换 w1 和 w2<br> | 高效交换指针内容,不影响引用计数。 |
reset | 释放所有权并置空 | void reset() noexcept; | cpp<br>w2.reset(); // w2 不再观测任何对象<br> | 将 weak_ptr 置为空状态,不影响关联的 shared_ptr 。 |
use_count | 获取关联 shared_ptr 的引用计数 | long use_count() const noexcept; | cpp<br>if (w2.use_count() > 0) { /* ... */ }<br> | 返回当前观测的 shared_ptr 的引用计数(可能为 0)。 |
expired | 检查对象是否已被释放 | bool expired() const noexcept; | cpp<br>if (!w2.expired()) { /* ... */ }<br> | 等价于 use_count() == 0 ,但更高效。 |
lock | 获取关联的 shared_ptr | shared_ptr<T> lock() const noexcept; | cpp<br>if (auto s = w2.lock()) { /* 安全使用 s */ }<br> | 若对象未释放,返回一个有效的 shared_ptr (增加引用计数);否则返回空。 |
owner_before | 提供基于所有权的排序 | template<class Y> bool owner_before(const shared_ptr<Y>& other) const; template<class Y> bool owner_before(const weak_ptr<Y>& other) const; | cpp<br>std::set<std::weak_ptr<int>, std::owner_less<std::weak_ptr<int>>> s;<br> | 用于将 weak_ptr 作为关联容器(如 std::set )的键,基于内部控制块排序。 |
关键说明:
- 线程安全:
weak_ptr
的引用计数操作是原子的,但需注意竞态条件(例如expired()
和lock()
之间可能有其他线程释放对象)。 - 循环引用:
weak_ptr
主要用于解决shared_ptr
的循环引用问题(例如双向链表、观察者模式)。 - 性能:
lock()
是原子操作,可能比expired()
+lock()
组合更高效。
如果需要进一步补充或调整,请随时指出!
文章目录