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

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;
}

关键改进点

  1. 统一调用接口:CallableBase提供模板化的operator(),内部调用纯虚函数call,由派生类实现具体逻辑,避免外部的类型转换操作。
  2. 自动推导可调用类型:新增register_method模板函数,自动推导传入的可调用对象(lambda、函数指针等)的类型,无需手动包装std::function,减少类型不匹配的风险。
  3. 使用std::invoke处理调用:Callable的call方法用std::invoke调用存储的函数,支持所有可调用对象(包括成员函数、lambda等),同时自动处理参数类型转换(比如字符串字面量转std::string)。
  4. 安全的查找逻辑:execute函数先查找methods_中是否存在目标方法,避免默认构造空shared_ptr导致的空指针问题。
  5. 完美转发保留值类别:所有参数都通过std::forward转发,确保移动语义和常量引用的正确性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 13:09:55