文章目录

C++智能指针std::shared_ptr的循环引用解决方案

发布于 2026-04-02 19:19:15 · 浏览 8 次 · 评论 0 条

C++智能指针std::shared_ptr的循环引用解决方案

C++标准库中的std::shared_ptr通过引用计数自动管理动态内存,但当两个或多个对象互相持有对方的shared_ptr时,会形成循环引用,导致引用计数永远不会归零,从而引发内存泄漏。解决这一问题的核心方法是使用std::weak_ptr打破循环。


识别循环引用场景

观察以下典型结构:两个类(如ParentChild)互相包含指向对方的shared_ptr成员变量。例如:

#include <memory>
#include <iostream>

class Child;
class Parent {
public:
    std::shared_ptr<Child> child_ptr;
    ~Parent() { std::cout << "Parent destroyed\n"; }
};

class Child {
public:
    std::shared_ptr<Parent> parent_ptr;
    ~Child() { std::cout << "Child destroyed\n"; }
};

创建相互引用的对象后,即使离开作用域,析构函数也不会被调用:

void create_cycle() {
    auto parent = std::make_shared<Parent>();
    auto child = std::make_shared<Child>();
    parent->child_ptr = child;
    child->parent_ptr = parent;
} // 函数结束,但对象未销毁

运行此代码会发现控制台无任何析构输出,证明内存未被释放。


使用std::weak_ptr打破循环

std::weak_ptr是一种不增加引用计数的观察型智能指针。它必须由shared_ptr构造,并通过lock()方法临时转换为shared_ptr以访问对象。

修改其中一个方向的指针类型为weak_ptr。通常将“从属”关系的一方设为弱引用(如Child持有Parent的弱引用):

#include <memory>
#include <iostream>

class Child;
class Parent {
public:
    std::shared_ptr<Child> child_ptr;
    ~Parent() { std::cout << "Parent destroyed\n"; }
};

class Child {
public:
    std::weak_ptr<Parent> parent_ptr; // 改为 weak_ptr
    ~Child() { std::cout << "Child destroyed\n"; }
};

重新测试对象生命周期:

void create_fixed_cycle() {
    auto parent = std::make_shared<Parent>();
    auto child = std::make_shared<Child>();
    parent->child_ptr = child;
    child->parent_ptr = parent; // weak_ptr 赋值
} // 函数结束,对象正常销毁

此时控制台会输出:

Child destroyed
Parent destroyed

证明循环已被打破。


安全访问weak_ptr指向的对象

由于weak_ptr不保证对象存活,必须通过lock()获取临时shared_ptr再使用:

  1. 调用 weak_ptr::lock() 尝试获取有效的shared_ptr
  2. 检查返回的shared_ptr是否为空(即原对象是否已被销毁)。
  3. 仅当非空时才访问对象成员。
void access_parent_safely(const std::weak_ptr<Parent>& wp) {
    if (auto sp = wp.lock()) {
        // sp 是有效的 shared_ptr,可安全使用
        std::cout << "Parent is alive\n";
    } else {
        std::cout << "Parent already destroyed\n";
    }
}

切勿直接通过weak_ptr解引用(如*wp),这会导致编译错误。


常见应用场景与最佳实践

场景 强引用方向 弱引用方向
父子对象(如树节点) 父 → 子 子 → 父
观察者模式 被观察者 → 观察者列表 观察者 → 被观察者
缓存系统 缓存容器 → 对象 对象 → 缓存容器(若需要反向查找)

遵循以下原则选择弱引用方向:

  • 保持“拥有权”关系的指针为shared_ptr(如容器拥有元素)。
  • “反向导航”或“观察”关系的指针设为weak_ptr
  • 避免在性能敏感路径频繁调用lock();若需多次访问,应缓存lock()结果。

注意weak_ptr不能直接解引用,也不能用于数组或自定义删除器场景(除非配合shared_ptr)。


验证修复效果

编写完整测试程序确认内存正确释放:

#include <memory>
#include <iostream>

class Child;
class Parent {
public:
    std::shared_ptr<Child> child_ptr;
    ~Parent() { std::cout << "Parent destroyed\n"; }
};

class Child {
public:
    std::weak_ptr<Parent> parent_ptr;
    ~Child() { std::cout << "Child destroyed\n"; }

    void print_parent_status() {
        if (parent_ptr.lock()) {
            std::cout << "Parent is alive during Child's lifetime\n";
        }
    }
};

int main() {
    {
        auto parent = std::make_shared<Parent>();
        auto child = std::make_shared<Child>();
        parent->child_ptr = child;
        child->parent_ptr = parent;
        child->print_parent_status();
    } // 作用域结束
    std::cout << "Exited scope\n";
    return 0;
}

预期输出

Parent is alive during Child's lifetime
Child destroyed
Parent destroyed
Exited scope

析构顺序表明:child先销毁(因其被parent强引用),随后parent因无外部引用而销毁。循环引用问题已解决。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文