You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

当捕获的资源可能被销毁时,如何正确创建std::function?

正确处理捕获可能销毁资源的std::function回调

在你给出的场景中,lambda捕获裸指针后,对象销毁时调用std::function会导致悬空指针访问(行为未定义)。以下是几种针对不同场景的解决方案,适配你提到的“无法直接改为std::shared_ptr”的实际情况:

方案1:利用std::weak_ptr + std::enable_shared_from_this(推荐)

如果你的类实例本质上是被std::shared_ptr管理的(即使是传递自身),可以让类继承std::enable_shared_from_this,通过生成弱引用(weak_ptr)来捕获,调用时先验证对象是否存活:

#include <memory>
#include <functional>

struct Foo : std::enable_shared_from_this<Foo> {
    int i = 0;
    void addInt(int input) {
        i += input;
    }

    // 生成绑定自身的回调函数
    std::function<void(int)> createCallback() {
        auto weak_self = weak_from_this();
        return [weak_self](int b) {
            // 尝试将弱引用转换为强引用,验证对象是否存活
            if (auto self = weak_self.lock()) {
                self->addInt(b);
            }
            // 对象已销毁时,可选择跳过操作或执行错误处理
        };
    }
};

int main() {
    auto foo = std::make_shared<Foo>();
    std::function<void(int)> lambda = foo->createCallback();

    lambda(8); // 正常执行

    foo.reset(); // 销毁Foo实例
    lambda(8); // 检测到对象已销毁,无操作,避免悬空访问
    return 0;
}

原理:enable_shared_from_this允许对象获取自身的shared_ptr,进而生成weak_ptr。lambda捕获weak_ptr而非裸指针,调用时通过lock()转换为shared_ptr——如果对象已销毁,lock()返回空,此时不会执行访问对象的逻辑。

方案2:手动添加生命周期标记(无智能指针场景)

如果无法使用智能指针,可以给类添加一个原子布尔标记,跟踪对象的存活状态,lambda调用前先检查标记:

#include <functional>
#include <atomic>

struct Foo {
    int i = 0;
    std::atomic<bool> is_alive{true}; // 标记对象是否存活

    ~Foo() {
        is_alive.store(false, std::memory_order_relaxed);
    }

    void addInt(int input) {
        i += input;
    }
};

int main() {
    Foo* lostMemory = new Foo();
    std::function<void(int)> lambda = [lostMemory](int b) {
        // 检查对象是否存活
        if (lostMemory->is_alive.load(std::memory_order_relaxed)) {
            lostMemory->addInt(b);
        }
    };

    lambda(8); // 正常执行

    delete lostMemory;
    lambda(8); // 检测到对象已销毁,不执行操作
    return 0;
}

注意:如果是多线程场景,要确保原子操作的内存顺序正确性,示例中用std::memory_order_relaxed足够满足标记可见性需求。

方案3:观察者模式主动管理回调生命周期

如果回调是注册到类内部的,可以在类销毁时主动清空所有关联的回调列表,避免外部调用时访问悬空对象:

#include <functional>
#include <vector>

struct Foo {
    int i = 0;
    std::vector<std::function<void(int)>> callbacks;

    ~Foo() {
        callbacks.clear(); // 销毁时清空所有回调
    }

    void addInt(int input) {
        i += input;
    }

    void registerCallback(std::function<void(int)> cb) {
        callbacks.push_back(std::move(cb));
    }

    void triggerCallbacks(int val) {
        for (auto& cb : callbacks) {
            cb(val);
        }
    }
};

int main() {
    auto foo = std::make_unique<Foo>();
    foo->registerCallback([foo = foo.get()](int b) {
        foo->addInt(b);
    });

    foo->triggerCallbacks(8); // 正常执行

    foo.reset(); // 销毁Foo实例,同时清空内部回调
    // 此时外部如果持有回调,调用时也不会访问到Foo(如果是内部触发的话)
    return 0;
}

这种方式更适合回调由类自身触发的场景,若回调被外部持有,仍需结合前两种方案的检查逻辑。


内容的提问来源于stack exchange,提问作者Natio2

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 02:37:46