weak_ptr不能直接访问对象,因为它不持有引用计数且未重载->和*操作符;必须先调用lock()获取shared_ptr再安全访问,lock()线程安全并避免竞态,expired()仅轻量判存活但存在竞态风险。

weak_ptr 为什么不能直接访问对象
因为 weak_ptr 不持有对象的引用计数,它只是“观察”一个由 shared_ptr 管理的对象。直接用 -> 或 * 会编译失败——weak_ptr 没有重载这些操作符。
正确做法是先调用 lock() 获取一个临时的 shared_ptr:
std::shared_ptr<int> sp = std::make_shared<int>(42);
std::weak_ptr<int> wp = sp;
auto locked = wp.lock(); // 返回 shared_ptr,若原对象已销毁则为空
if (locked) {
std::cout << *locked << "\n"; // 安全访问
}
-
lock()是线程安全的,返回的shared_ptr会增加引用计数(仅当对象还存在) - 不要用
expired()+lock()两次检查——竞态条件下可能失效,直接用lock()判空更可靠 - 避免长期持有
weak_ptr后反复调用lock():每次调用都有原子操作开销
循环引用破除:weak_ptr 的典型场景
两个 shared_ptr 相互持有会导致引用计数永远不为 0,内存无法释放。这时让其中一方改用 weak_ptr。
比如父子节点结构:
立即学习“C++免费学习笔记(深入)”;
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
struct Node {
std::shared_ptr<Node> parent;
std::shared_ptr<Node> child;
};
改成:
struct Node {
std::shared_ptr<Node> parent; // 父持有子:shared_ptr
std::weak_ptr<Node> child; // 子持有父:weak_ptr(避免循环)
};
- 子访问父时必须用
child.lock(),且要检查是否为空 - 注意:不能只把
parent改成weak_ptr—— 那样父节点可能被提前释放,导致悬空 - 谁“拥有”谁,决定了哪边用
shared_ptr;weak_ptr只用于非拥有关系
weak_ptr 构造和赋值的限制
weak_ptr 不能直接从裸指针或 unique_ptr 构造,只能从 shared_ptr 或另一个 weak_ptr 初始化。
- 合法:
std::weak_ptr<int> wp(sp);、std::weak_ptr<int> wp2(wp); - 非法:
std::weak_ptr<int> wp(new int(1));(编译错误) - 非法:
std::weak_ptr<int> wp(std::move(up));(unique_ptr无法转weak_ptr) - 也不能用
reset()——weak_ptr没有这个成员函数
判断对象是否已被释放的两种方式
常用的是 lock() 后判空,但有时只需要知道“是否还活着”,不想获取所有权,这时用 expired() 更轻量:
if (wp.expired()) {
// 对象已销毁,无需 lock
} else {
auto p = wp.lock(); // 此时 lock() 几乎无开销
}
-
expired()内部只检查引用计数是否为 0,比lock()少一次原子增操作 - 但
expired()和后续lock()之间仍有竞态窗口,所以多数情况下直接lock()更稳妥 - 多线程环境下,
expired()返回false并不能保证lock()一定成功
真正容易被忽略的是:weak_ptr 的生命周期本身不依赖所指对象,它自己可以一直存在,哪怕目标早已销毁——这时候它就变成一个“空观察者”,不会崩溃,但也不再有意义。

















