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

C++中用std::function兼容不同函数签名实现类C#事件系统的问题

问题核心

你说得对,想用单个std::function来存储任意签名的函数其实是行不通的——因为std::function本身是签名绑定的,它的模板参数就固定了它能接受的可调用对象的参数和返回值类型。比如std::function<void()>只能存储无参、返回值为void的可调用对象,一旦签名不匹配,编译器就会报错。

为什么第三个绑定失败?

看你给出的代码:

std::function<void()> fn = std::bind(FuncWithParam, _1);

这里std::bind(FuncWithParam, _1)生成的可调用对象是需要接收一个const std::string&参数的(_1是占位符,代表调用时要传入第一个参数),但你把它赋值给了无参的std::function<void()>,两者的签名完全不匹配,编译器自然会拒绝这种赋值。

可行的解决方案

要实现支持任意签名的C++事件系统,你需要用更灵活的类型处理方式,下面是几种常用方案:

1. 用std::variant存储多签名函数

如果你能提前枚举所有需要支持的函数签名,可以用std::variant来包装不同类型的std::function,调用时通过std::visit匹配类型:

#include <variant>
#include <functional>
#include <string>
#include <iostream>

// 定义支持的所有事件函数签名
using EventFunc = std::variant<
    std::function<void()>,
    std::function<void(const std::string&)>
>;

// 通用调用函数
void invokeEvent(EventFunc& func) {
    std::visit([](auto&& f) {
        using FuncType = std::decay_t<decltype(f)>;
        if constexpr (std::is_same_v<FuncType, std::function<void()>>) {
            f();
        } else if constexpr (std::is_same_v<FuncType, std::function<void(const std::string&)>>) {
            f("Triggered with param");
        }
    }, func);
}

// 测试函数
void Func() { std::cout << "Notified (no param)" << std::endl; }
void FuncWithParam(const std::string& str) { std::cout << "Received: " << str << std::endl; }

int main() {
    EventFunc fn1 = std::bind(Func);
    EventFunc fn2 = std::bind(FuncWithParam, "Pre-bound message");
    EventFunc fn3 = FuncWithParam; // 直接存储带参函数

    invokeEvent(fn1);
    invokeEvent(fn2);
    invokeEvent(fn3);
    return 0;
}

2. 基于类型擦除的通用事件系统

如果要支持完全任意的签名,可以自己实现类型擦除的事件槽:

#include <memory>
#include <functional>
#include <string>
#include <iostream>
#include <vector>

// 基类:统一所有事件槽的接口
class EventSlot {
public:
    virtual ~EventSlot() = default;
};

// 派生类:针对特定签名的事件槽
template<typename Signature>
class TypedEventSlot : public EventSlot {
public:
    using FuncType = std::function<Signature>;
    TypedEventSlot(FuncType func) : func_(std::move(func)) {}

    template<typename... Args>
    void invoke(Args&&... args) {
        func_(std::forward<Args>(args)...);
    }

private:
    FuncType func_;
};

// 事件类:管理所有订阅的事件槽
class Event {
public:
    // 添加任意签名的事件处理函数
    template<typename Signature, typename Func>
    void subscribe(Func&& func) {
        slots_.emplace_back(std::make_unique<TypedEventSlot<Signature>>(std::forward<Func>(func)));
    }

    // 触发事件,传入对应参数
    template<typename... Args>
    void trigger(Args&&... args) {
        for (auto& slot : slots_) {
            // 尝试转换为对应签名的事件槽
            auto* typedSlot = dynamic_cast<TypedEventSlot<void(Args...)>*>(slot.get());
            if (typedSlot) {
                typedSlot->invoke(std::forward<Args>(args)...);
            }
        }
    }

private:
    std::vector<std::unique_ptr<EventSlot>> slots_;
};

// 测试函数
void Func() { std::cout << "Notified (no param)" << std::endl; }
void FuncWithParam(const std::string& str) { std::cout << "Received: " << str << std::endl; }

int main() {
    Event myEvent;
    myEvent.subscribe<void()>(Func);
    myEvent.subscribe<void(const std::string&)>(FuncWithParam);
    myEvent.subscribe<void(const std::string&)>(std::bind(FuncWithParam, "Bound message"));

    // 触发无参事件(只有Func响应)
    myEvent.trigger();
    // 触发带参事件(两个带参函数响应)
    myEvent.trigger("Hello from trigger");
    return 0;
}

3. 贴近C#风格的统一签名方案

如果想更贴近C#的事件模型,通常会统一事件处理函数的签名,比如定义一个包含事件参数的基类,让所有事件处理函数都接收这个参数对象:

#include <functional>
#include <string>
#include <iostream>
#include <vector>

// 事件参数基类
class EventArgs {
public:
    virtual ~EventArgs() = default;
};

// 带字符串参数的事件参数
class StringEventArgs : public EventArgs {
public:
    std::string message;
    StringEventArgs(const std::string& msg) : message(msg) {}
};

// 统一的事件处理函数签名
using EventHandler = std::function<void(EventArgs*)>;

// 事件类
class Event {
public:
    void addHandler(EventHandler handler) {
        handlers_.push_back(std::move(handler));
    }

    void trigger(EventArgs* args) {
        for (auto& handler : handlers_) {
            handler(args);
        }
    }

private:
    std::vector<EventHandler> handlers_;
};

// 测试函数
void Func(EventArgs*) { std::cout << "Notified (no param)" << std::endl; }
void FuncWithParam(EventArgs* args) {
    if (auto strArgs = dynamic_cast<StringEventArgs*>(args)) {
        std::cout << "Received: " << strArgs->message << std::endl;
    }
}

int main() {
    Event myEvent;
    myEvent.addHandler(Func);
    myEvent.addHandler(FuncWithParam);

    myEvent.trigger(nullptr); // 触发无参事件
    myEvent.trigger(new StringEventArgs("Hello C# style")); // 触发带参事件
    return 0;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 06:47:20