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

如何让std::function引用可调用对象?基于std::function实现发布订阅疑问

我来帮你拆解这个问题,一步步捋清楚哈~

关于std::function与可调用对象的引用问题

首先看你代码里的场景:当你调用sub.Subscribe(a)时,确实会复制Test对象。因为std::function默认会把传入的可调用对象完整拷贝到自己的内部存储中,这里的a作为Test实例,会被复制一份存到sub.callback_里。

如何让std::function引用可调用对象?

有两种常用方式可以让std::function持有对象的引用而非拷贝,同时要注意对象生命周期的问题:

1. 用std::ref/std::cref包装对象

std::ref(非const引用)或std::cref(const引用)可以把对象包装成引用包装器,让std::function存储这个包装器,从而间接持有原对象的引用:

#include <functional>
#include <iostream>

using Callback = std::function<void ()>;
struct Test {
    void operator()() { std::cout << a << std::endl; };
    int a;
};
struct Sub {
    void Subscribe(Callback callback) { callback_ = callback; }
    void trigger() { callback_(); }
    Callback callback_;
};

int main() {
    Test a = {1};
    Sub sub;
    // 用std::ref包装a,让std::function持有引用
    sub.Subscribe(std::ref(a));
    a.a = 2; // 修改原对象的值
    sub.trigger(); // 会输出2,说明引用生效
    return 0;
}

⚠️ 重点提醒:必须保证被引用的对象a生命周期长于sub,否则sub.callback_会变成悬空引用,调用时触发未定义行为。

2. 用lambda捕获引用

可以通过lambda表达式捕获Test对象的引用,再把lambda传给std::function,lambda内部会直接引用原对象:

int main() {
    Test a = {1};
    Sub sub;
    // 捕获a的引用
    sub.Subscribe([&a]() { a(); });
    a.a = 2;
    sub.trigger(); // 输出2
    return 0;
}

同样要注意:lambda捕获的引用不能在原对象销毁后使用,否则会出问题。

其他实现建议

1. 支持多订阅者

你的当前实现只能存储一个回调,实际发布订阅模式通常需要支持多个订阅者。可以把callback_改成容器(比如std::vector<Callback>),让Subscribe负责添加回调,trigger遍历调用所有回调:

struct Sub {
    void Subscribe(Callback callback) { callbacks_.push_back(std::move(callback)); }
    void trigger() {
        for (auto& cb : callbacks_) {
            cb();
        }
    }
    std::vector<Callback> callbacks_;
};

用std::move可以避免不必要的拷贝,提升效率。

2. 模板替代std::function(性能优化)

如果不需要类型擦除的灵活性,可用模板定义Subscribe方法,避免std::function的运行时开销:

struct Sub {
    template<typename Func>
    void Subscribe(Func&& func) {
        callback_ = std::forward<Func>(func);
    }
    void trigger() { callback_(); }
    std::function<void()> callback_;
};
// 更极端的情况:如果不需要存储不同类型的回调,甚至可以直接存储模板类型
template<typename Func>
struct SubTemplate {
    SubTemplate(Func&& func) : callback_(std::forward<Func>(func)) {}
    void trigger() { callback_(); }
    Func callback_;
};

这种方式在性能敏感场景更优,但牺牲了类型擦除的灵活性。

3. 智能指针管理生命周期

如果担心对象生命周期不确定,可用std::shared_ptr管理Test对象,让std::function持有shared_ptr,只要有回调存在,对象就不会被销毁:

int main() {
    auto a = std::make_shared<Test>();
    a->a = 1;
    Sub sub;
    sub.Subscribe([a]() { (*a)(); });
    a->a = 2;
    sub.trigger(); // 输出2
    return 0;
}

这种方式适合对象生命周期难以把控的场景,彻底避免悬空引用。

4. 线程安全优化

如果发布订阅在多线程环境使用,要给订阅、触发操作加互斥锁,避免竞态条件:

#include <mutex>

struct Sub {
    void Subscribe(Callback callback) {
        std::lock_guard<std::mutex> lock(mtx_);
        callbacks_.push_back(std::move(callback));
    }
    void trigger() {
        std::lock_guard<std::mutex> lock(mtx_);
        for (auto& cb : callbacks_) {
            cb();
        }
    }
    std::vector<Callback> callbacks_;
    std::mutex mtx_;
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 03:49:06