文章目录

C++智能指针enable_shared_from_this的循环引用解决

发布于 2026-04-21 00:20:09 · 浏览 7 次 · 评论 0 条

C++智能指针enable_shared_from_this的循环引用解决

在C++开发中,使用 std::shared_ptr 管理对象生命周期时,常遇到两个核心痛点:如何在类成员函数中安全地获取指向当前对象的 shared_ptr,以及如何由此引发的循环引用导致内存泄漏问题。直接传递 this 指针会导致独立的引用计数块,造成对象被多次销毁;而对象间相互持有 shared_ptr 则会导致引用计数归零失败。

本指南将详细介绍如何结合使用 std::enable_shared_from_thisstd::weak_ptr 来彻底解决这一技术难题。


1. 识别问题的根源

在编写涉及回调或异步操作的类时,经常需要将当前对象的指针传递给另一个对象。如果直接使用 this,并在接收方将其封装为 std::shared_ptr,将引发严重的内存错误。

错误操作示例:

class Node {
public:
    void setCallback() {
        // 错误:直接使用 this 构造新的 shared_ptr
        // 这会创建一个新的、独立的引用计数块,与现有的 shared_ptr 管理断开
        std::shared_ptr<Node> self(this); 
    }
};

后果逻辑分析:

当上述代码执行时,内存中会出现两个互不相干的引用计数控制系统。

graph TD A[堆内存对象] -->|被原始 shared_ptr 管理| B[引用计数块 1] A -->|被 this 新建的 shared_ptr 管理| C[引用计数块 2] C -->|计数归零| D[析构对象] B -->|成为悬空指针| E[访问野指针崩溃]

2. 启用安全自引用机制

为了在类成员函数内部获取一个与当前对象引用计数关联的 shared_ptr,C++ 提供了 std::enable_shared_from_this 模板基类。

执行步骤:

  1. 继承 模板类 std::enable_shared_from_this,并将当前类名作为模板参数传递。
  2. 确保对象本身是通过 std::make_sharedstd::shared_ptr 构造的,不能是栈对象或裸指针new的对象。

代码实现:

#include <memory>
#include <iostream>

class Controller : public std::enable_shared_from_this<Controller> {
public:
    // 一定要通过 shared_ptr 构造,否则 shared_from_this 会抛出异常
    static std::shared_ptr<Controller> create() {
        return std::make_shared<Controller>();
    }

    void registerSelf() {
        // 正确:调用 shared_from_this() 获取关联的 shared_ptr
        std::shared_ptr<Controller> self = shared_from_this();
        std::cout << "Registering self, use_count: " << self.use_count() << std::endl;
    }
};

3. 解决循环引用

循环引用通常发生在两个对象相互持有对方的 shared_ptr。例如,Controller 持有 Worker 的强引用,Worker 回调时也持有 Controller 的强引用。此时引用计数永远不会降为 0。

解决方案策略:

  1. 确定持有关系:决定谁是“所有者”,谁是“观察者”。
  2. 打破环:在“观察者”一方,将持有的 shared_ptr 替换为 std::weak_ptr
  3. 传递指针:在建立联系时,使用 shared_from_this() 传递 Controller 的指针。

操作步骤:

  1. Worker 类中,定义成员变量为 std::weak_ptr<Controller>
  2. Worker 的方法中,调用 lock() 方法将 weak_ptr 提升为 shared_ptr 以便临时使用(使用前必须检查是否有效)。
  3. Controller 类中,调用 shared_from_this() 将自身传递给 Worker

完整代码示例:

#include <iostream>
#include <memory>
#include <string>

// 前置声明
class Worker;

class Controller : public std::enable_shared_from_this<Controller> {
public:
    std::string name;
    std::shared_ptr<Worker> worker;

    Controller(std::string n) : name(n) {
        std::cout << "Controller " << name << " Created.\n";
    }

    ~Controller() {
        std::cout << "Controller " << name << " Destroyed.\n";
    }

    void setWorker(std::shared_ptr<Worker> w) {
        worker = w;
        // 关键步骤:将当前对象的 shared_ptr 传递给 Worker
        // 使用 shared_from_this 而不是 this
        worker->setController(shared_from_this());
    }
};

class Worker {
public:
    // 使用 weak_ptr 打破循环引用
    std::weak_ptr<Controller> controller;

    void setController(std::shared_ptr<Controller> c) {
        controller = c;
    }

    void doWork() {
        // 必须先 lock() 提升为 shared_ptr
        if (auto ptr = controller.lock()) {
            std::cout << "Worker is working for " << ptr->name << "\n";
        } else {
            std::cout << "Controller is gone.\n";
        }
    }
};

int main() {
    // 1. 创建 Controller
    auto ctrl = std::make_shared<Controller>("MainServer");

    // 2. 创建 Worker
    auto wk = std::make_shared<Worker>();

    // 3. 建立关系
    // ctrl 持有 wk (shared_ptr)
    // wk 持有 ctrl (weak_ptr)
    ctrl->setWorker(wk);

    // 4. 模拟工作
    wk->doWork();

    // 5. 结束作用域
    // ctrl 引用计数归零,析构
    // wk 中的 weak_ptr 不阻止 ctrl 销毁
    // wk 随后也因 ctrl 析构而释放(这里 ctrl 也不持有 wk 的引用了? 
    // 注意:本例中 ctrl 持有 wk 的 shared_ptr,所以 wk 在 ctrl 析构后才会析构,
    // 但因为没有外部强引用指向 wk,ctrl 销毁后 wk 的强引用计数归零,随之销毁。

    return 0;
}

执行逻辑解析:

下表展示了引用计数的变化情况及对象生命周期。

阶段 ctrl (强引用计数) wk (强引用计数) wk.controller (弱引用) 状态说明
创建对象 1 1 0 对象独立存在
建立关系 1 2 1 ctrl 引用 wkwk 弱引用 ctrl
作用域结束 0 (析构) 1 (随后变为0) - ctrl 销毁,wk 失去持有者,随之销毁
最终结果 0 0 - 内存正常释放,无泄漏

4. 关键注意事项总结

在使用此模式时,请务必遵守以下规则,否则可能导致程序崩溃:

  1. 禁止在构造函数中调用 shared_from_this()

    • 原因:在构造函数执行期间,std::shared_ptr 对象尚未完全构建完成,内部的 weak_ptr 初始化机制尚未生效。此时调用会抛出 std::bad_weak_ptr 异常。
  2. 检查 lock() 返回值

    • weak_ptr::lock() 可能返回空的 shared_ptr(如果对象已被销毁)。直接使用返回的指针而不判空是极其危险的。
  3. 不要混合使用裸指针和智能指针

    • 如果一个对象被 shared_ptr 管理,就不要在其他地方保存它的裸 this 指针并试图将其转回 shared_ptr,除非你使用 shared_from_this

通过上述步骤,你可以构建出既安全又无内存泄漏的 C++ 对象网络。

评论 (0)

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

扫一扫,手机查看

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