C++ RPC服务器函数注册:非内置类型调用崩溃问题求助
问题原因分析
你的代码出现段错误的核心原因是**dynamic_cast类型转换失败,返回空指针后被解引用**,具体触发场景:
当你调用execute时,模板参数Args是根据传入的实参类型推导出来的,而这个推导结果和你注册时std::function的参数类型不匹配,导致dynamic_cast无法将基类指针转换为目标派生类指针,最终返回nullptr,调用f_ptr->execute时触发段错误。
举个例子:
- 注册时你存储的是
Callable<std::function<void(std::string)>> - 调用时传入字符串字面量
"test",模板推导Args为const char(&)[5],此时func_t是std::function<void(const char(&)[5])> dynamic_cast<Callable<func_t>*>尝试转换存储的Callable<std::function<void(std::string)>>*,因为两个Callable的模板参数类型完全不同,转换失败返回nullptr,后续解引用直接崩溃。
对于自定义TestObj类型,同理:如果注册时的std::function参数是TestObj,但调用时传入的是TestObj&&或者const TestObj&,推导的Args类型和注册时的参数类型不一致,都会导致转换失败。
另外,你的execute函数还有一个隐患:如果methods_中不存在对应的name,methods_[name]会默认构造一个空的shared_ptr,此时methods_[name].get()是nullptr,dynamic_cast也会返回nullptr,同样触发段错误。
修复方案
我们需要重构类型擦除的逻辑,让CallableBase提供统一的调用接口,不再依赖外部推导类型进行dynamic_cast。核心思路是:让每个Callable派生类自己负责处理参数的接收和转发,外部调用时只需要传入参数,不需要关心内部的函数类型。
重构后的完整代码
#include <functional> #include <string> #include <memory> #include <iostream> #include <vector> #include <unordered_map> #include <utility> using namespace std; class CallableBase { public: virtual ~CallableBase() = default; // 统一的调用接口,接收任意参数包 template <class... Args> void operator()(Args&&... args) { call(std::forward<Args>(args)...); } private: // 纯虚函数,由派生类实现具体调用逻辑 virtual void call(auto&&... args) = 0; }; template <class Func> class Callable : public CallableBase { Func f_; public: explicit Callable(Func func) : f_(std::move(func)) {} private: void call(auto&&... args) override { // 完美转发参数给存储的函数,自动处理类型转换 std::invoke(f_, std::forward<decltype(args)>(args)...); } }; unordered_map<string, shared_ptr<CallableBase>> methods_; // 注册函数:自动推导可调用对象的类型,无需手动指定std::function template <class Func> void register_method(const string& name, Func func) { methods_[name] = make_shared<Callable<Func>>(std::move(func)); } // 执行函数:直接调用统一的operator()接口 template <class... Args> void execute(const string& name, Args&&... args) { auto it = methods_.find(name); if (it == methods_.end()) { cerr << "Method " << name << " not found" << endl; return; } (*it->second)(std::forward<Args>(args)...); } class TestObj { public: TestObj() = default; TestObj(const TestObj& o) { cout << "Copy Constructor. " << endl; } TestObj(TestObj&& o) { cout << "Move Constructor. " << endl; } ~TestObj() = default; void Nap() const { cout << a << endl; } int a = 2; }; int main() { // 注册时无需手动包装std::function,直接传入lambda register_method("echo", [](int s){ cout << s << endl; }); register_method("add", [](int a, int b, int& c){ c = a + b; }); register_method("sub", [](int a, int b, int& c){ c = a - b; }); int outer = 1; register_method("modify_outer", [&outer](int a, int b){ outer = a + b; }); // 测试内置类型 execute("modify_outer", 1, 2); cout << outer << endl; execute("echo", 12); int c = 0; execute("add", 2, 3, c); cout << c << endl; execute("sub", 2, 3, c); cout << c << endl; // 测试std::string类型 register_method("print_str", [](string s){ cout << "String: " << s << endl; }); execute("print_str", "Hello World"); // 自动转换为std::string // 测试自定义TestObj类型 register_method("print_obj", [](TestObj obj){ obj.Nap(); }); TestObj obj; execute("print_obj", obj); // 调用拷贝构造 execute("print_obj", std::move(obj)); // 调用移动构造 return 0; }
关键改进点
- 统一调用接口:
CallableBase提供模板化的operator(),内部调用纯虚函数call,由派生类实现具体逻辑,避免外部的类型转换操作。 - 自动推导可调用类型:新增
register_method模板函数,自动推导传入的可调用对象(lambda、函数指针等)的类型,无需手动包装std::function,减少类型不匹配的风险。 - 使用
std::invoke处理调用:Callable的call方法用std::invoke调用存储的函数,支持所有可调用对象(包括成员函数、lambda等),同时自动处理参数类型转换(比如字符串字面量转std::string)。 - 安全的查找逻辑:
execute函数先查找methods_中是否存在目标方法,避免默认构造空shared_ptr导致的空指针问题。 - 完美转发保留值类别:所有参数都通过
std::forward转发,确保移动语义和常量引用的正确性。
内容的提问来源于stack exchange,提问作者Mengyu Chen
相关产品推荐
相关产品推荐

