通用函数指针存储与调用:Caller类Invoke方法实现疑问
解决方案
问题1:存储不同签名的函数指针
要统一存储不同签名的函数指针,核心思路是类型擦除——把不同类型的函数包装成统一的可存储类型。这里推荐两种实用方案:
方案一:用std::any+std::function实现
借助C++17的std::any实现类型擦除,配合std::function包装函数,用哈希表存储函数名与包装后对象的映射:
#include <unordered_map> #include <any> #include <functional> #include <vector> #include <stdexcept> class Caller { private: std::unordered_map<std::string, std::any> func_table; public: // 模板注册方法,支持任意void返回值的函数 template<typename Func> void Register(const std::string& name, Func func) { func_table[name] = std::function(std::move(func)); } // 可变模板参数版Invoke,替代C风格可变参数 template<typename... Args> void Invoke(const std::string& function_name, Args&&... args) { auto it = func_table.find(function_name); if (it == func_table.end()) { throw std::runtime_error("Function not found: " + function_name); } try { // 将std::any转换为对应签名的std::function,再调用 std::invoke( std::any_cast<std::function<void(Args...)>>(it->second), std::forward<Args>(args)... ); } catch (const std::bad_any_cast& e) { throw std::runtime_error("Parameter mismatch for function: " + function_name); } } };
方案二:自定义抽象基类实现
如果不能使用C++17的std::any,可以定义抽象基类作为统一接口,为每个函数签名创建派生类存储函数指针:
#include <unordered_map> #include <vector> #include <stdexcept> class FunctionWrapper { public: virtual ~FunctionWrapper() = default; }; template<typename Func> class ConcreteFunction : public FunctionWrapper { private: Func func; public: ConcreteFunction(Func f) : func(std::move(f)) {} Func& GetFunc() { return func; } }; class Caller { private: std::unordered_map<std::string, FunctionWrapper*> func_table; public: ~Caller() { for (auto& pair : func_table) { delete pair.second; } } template<typename Func> void Register(const std::string& name, Func func) { func_table[name] = new ConcreteFunction<Func>(std::move(func)); } template<typename... Args> void Invoke(const std::string& function_name, Args&&... args) { auto it = func_table.find(function_name); if (it == func_table.end()) { throw std::runtime_error("Function not found: " + function_name); } auto wrapper = dynamic_cast<ConcreteFunction<std::function<void(Args...)>>*>(it->second); if (!wrapper) { throw std::runtime_error("Parameter mismatch for function: " + function_name); } std::invoke(wrapper->GetFunc(), std::forward<Args>(args)...); } };
问题2:Invoke传递参数的实现
C风格的可变参数(...)缺乏类型安全,且无法在编译期获取参数信息,强烈推荐使用C++11引入的可变模板参数实现Invoke:
- 用
template<typename... Args>定义参数包,接收任意数量、类型的参数; - 通过
std::forward<Args>(args)...完美转发参数,减少不必要的拷贝; - 借助
std::invoke调用函数,它能兼容普通函数、Lambda、成员函数等多种可调用对象。
完整使用示例
// 目标函数 void doSomething(int arg) { /* 实现逻辑 */ } void doSomethingElse(int x, float y, const std::vector<int>& z) { /* 实现逻辑 */ } // 调用示例 void MyFunc(Caller* caller, const std::vector<int>& data) { caller->Invoke("doSomething", 10); caller->Invoke("doSomethingElse", 10, 1.0f, data); } int main() { Caller caller; // 注册函数 caller.Register("doSomething", &doSomething); caller.Register("doSomethingElse", &doSomethingElse); std::vector<int> data = {1, 2, 3}; MyFunc(&caller, data); return 0; }
不推荐的C风格可变参数方案
如果必须使用C风格...,只能硬编码每个函数的参数解析逻辑,这种方式类型不安全且扩展性极差:
void Invoke(const char* function_name, ...) { va_list args; va_start(args, function_name); if (strcmp(function_name, "doSomething") == 0) { int arg = va_arg(args, int); doSomething(arg); } else if (strcmp(function_name, "doSomethingElse") == 0) { int x = va_arg(args, int); float y = va_arg(args, float); // 引用类型会退化为指针,需手动转换,极易出错 const std::vector<int>* z_ptr = va_arg(args, const std::vector<int>*); doSomethingElse(x, y, *z_ptr); } va_end(args); }
内容的提问来源于stack exchange,提问作者jg_at_vividq
相关产品推荐
相关产品推荐

