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

如何将不同签名的函数指针存储到同一个数组或std::unordered_map中?

实现方案说明

C++是静态类型语言,原生不支持不同签名函数直接无约束调用,但可以通过类型擦除实现你需要的存储+调用需求,以下是两种适配你场景的成熟方案:


方案1:基于std::any的轻量实现

这是最低成本的实现方案,核心是用std::any做类型擦除存储任意函数指针,调用时显式指定签名转换即可,适配任意返回值场景:

#include <unordered_map>
#include <string>
#include <any>
#include <iostream>

// 测试函数
void funcA(std::string str) {
    std::cout << "funcA调用:传入参数为" << str << std::endl;
}
void funcB(int32_t i, int32_t j) {
    std::cout << "funcB调用:参数和为" << i + j << std::endl;
}
int funcC(int a) { // 带返回值的测试函数
    return a * 2;
}

int main() {
    std::unordered_map<std::string, std::any> func_map;
    // 存入任意签名的函数
    func_map.emplace("funcA", &funcA);
    func_map.emplace("funcB", &funcB);
    func_map.emplace("funcC", &funcC);

    // 调用时显式转换为对应签名的函数指针
    std::any_cast<void(*)(std::string)>(func_map["funcA"])("test");
    std::any_cast<void(*)(int32_t, int32_t)>(func_map["funcB"])(3,4);
    int res = std::any_cast<int(*)(int)>(func_map["funcC"])(5);
    std::cout << "funcC返回值:" << res << std::endl;
    return 0;
}
  • 优点:无额外封装成本,支持存储任意可调用对象,适配所有返回值类型
  • 缺点:调用时需要提前知道函数签名,无法直接实现你示例中的map["funcA"]("test")语法,签名写错会抛出std::bad_any_cast异常

方案2:封装类型擦除类,实现目标调用语法

这是游戏开发中回调/事件管理器的常用方案,通过封装自定义可调用类,重载operator()实现你需要的直接调用效果,同时支持任意返回值:

#include <unordered_map>
#include <string>
#include <any>
#include <functional>
#include <stdexcept>
#include <iostream>

class AnyFunc {
private:
    std::any m_func;
public:
    // 支持任意可调用对象的构造
    template<typename Func>
    AnyFunc(Func&& f) : m_func(std::forward<Func>(f)) {}

    // 重载调用运算符,自动匹配签名
    template<typename... Args>
    decltype(auto) operator()(Args&&... args) const {
        // 匹配std::function类型
        using FuncType = std::function<std::invoke_result_t<AnyFunc, Args...>(Args...)>;
        if (auto* f = std::any_cast<FuncType>(&m_func)) {
            return (*f)(std::forward<Args>(args)...);
        }
        // 匹配原生函数指针类型
        using RawFuncType = std::invoke_result_t<AnyFunc, Args...>(*)(Args...);
        if (auto* f = std::any_cast<RawFuncType>(&m_func)) {
            return (*f)(std::forward<Args>(args)...);
        }
        throw std::runtime_error("回调函数签名不匹配");
    }
};

// 测试函数
void funcA(std::string str) {
    std::cout << "funcA调用:传入参数为" << str << std::endl;
}
void funcB(int32_t i, int32_t j) {
    std::cout << "funcB调用:参数和为" << i + j << std::endl;
}
float funcC(float a) {
    return a * 1.5f;
}

int main() {
    std::unordered_map<std::string, AnyFunc> func_map;
    func_map.emplace("funcA", &funcA);
    func_map.emplace("funcB", &funcB);
    func_map.emplace("funcC", &funcC);

    // 完全符合你要求的调用语法
    func_map["funcA"]("test");
    func_map["funcB"](3,4);
    float res = func_map["funcC"](2.0f);
    std::cout << "funcC返回值:" << res << std::endl;
    return 0;
}
  • 优点:完全匹配你需要的调用写法,支持任意签名/返回值的可调用对象,可扩展错误处理、参数校验逻辑,非常适合游戏事件系统场景
  • 缺点:签名不匹配为运行时报错,你可以通过约定同一事件的参数格式规避这个问题

游戏事件管理器的优化建议

如果是做商业级游戏的事件系统,更推荐按事件类型分类存储回调:给每个事件定义固定的参数结构体,同一事件的回调签名保持一致,既可以做到编译期类型安全,也没有运行期类型转换的性能开销,更适配游戏的高性能要求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 22:36:03