题目

编写程序,模拟图或双向链表中的对象互引,揭示 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 均已彻底安全析构,无任何内存泄露!