题目
编写程序,模拟图或双向链表中的对象互引,揭示 std::shared_ptr 的头号死穴——循环引用(Circular Reference)。当节点 A 与节点 B 内部均持有对方的 shared_ptr 时,各自计数永远停留在 2,导致离开作用域后析构函数永远无法被触发(内存永久泄漏);随后将其中一个反向引用重构为 std::weak_ptr(不增加强引用计数),并使用 lock() 探测对象生命周期,彻底化解循环引用。
输入格式:无需外部输入。
输出格式:对比观察循环引用导致的对象未析构与使用 weak_ptr 后的正常析构回收。
数据范围与约定:循环引用成因、弱引用机制与 lock() 安全提升。
思路与算法
1. 循环引用的死锁闭环
A 持有 B 的 shared_ptr,B 持有 A 的 shared_ptr。离开作用域时,A 的析构依赖于 B 释放,B 的析构依赖于 A 释放,形成僵局,导致两者的析构函数永远不被执行!
2. weak_ptr 的弱观察者属性weak_ptr 只观察对象,绝对不增加强引用计数(use_count 保持不变)。因此不会阻碍对象的正常析构。
3. 安全访问机制 lock()weak_ptr 无法直接解引用(没有 * 和 -> 运算符)。访问前必须调用 auto sp = wp.lock();:若原对象依然健在,提升为一个临时的 shared_ptr 并安全访问;若已析构,返回 nullptr。
完整程序
#include <iostream>
#include <memory>
class NodeB; // 前置声明
class NodeA {
public:
std::shared_ptr<NodeB> bPtr; // 强引用 NodeB
NodeA() { std::cout << " [构造] NodeA\n"; }
~NodeA() { std::cout << " [析构] NodeA 安全释放\n"; }
};
class NodeB {
public:
// 关键修正:使用 weak_ptr 弱引用打破强引用循环闭环!
std::weak_ptr<NodeA> aWeakPtr;
NodeB() { std::cout << " [构造] NodeB\n"; }
~NodeB() { std::cout << " [析构] NodeB 安全释放\n"; }
void visitA() {
// 使用 lock() 安全提升为 shared_ptr
if (auto lockedA = aWeakPtr.lock()) {
std::cout << " [weak_ptr lock 成功] NodeA 依然存活,安全访问成功\n";
} else {
std::cout << " [weak_ptr lock 失败] NodeA 已被销毁,拒绝悬垂访问\n";
}
}
};
int main() {
std::cout << "=== 使用 weak_ptr 成功解决循环引用内存泄漏 ===\n";
{
auto a = std::make_shared<NodeA>();
auto b = std::make_shared<NodeB>();
a->bPtr = b; // A 强引用 B
b->aWeakPtr = a; // B 弱引用 A (不增加 A 的引用计数!)
std::cout << "当前 a 的强引用计数 use_count: " << a.use_count() << " (仅为 1!)\n";
std::cout << "当前 b 的强引用计数 use_count: " << b.use_count() << "\n";
b->visitA();
std::cout << "准备离开局部作用域...\n";
} // 作用域结束,A 和 B 均能顺畅析构释放
std::cout << "验证完毕:A 与 B 均已彻底安全析构,无任何内存泄露!\n";
return 0;
}运行示例
输出:
=== 使用 weak_ptr 成功解决循环引用内存泄漏 ===
[构造] NodeA
[构造] NodeB
当前 a 的强引用计数 use_count: 1 (仅为 1!)
当前 b 的强引用计数 use_count: 2
[weak_ptr lock 成功] NodeA 依然存活,安全访问成功
准备离开局部作用域...
[析构] NodeA 安全释放
[析构] NodeB 安全释放
验证完毕:A 与 B 均已彻底安全析构,无任何内存泄露!