2

I want to implement a RPC server. We can register some method on it. for example:

server.register("echo", [](){ cout << "hello" << endl; });
server.register("add", [](int a, int b, int &c){ c = a+b; });
server.register("print", [](std::string s){ cout << s << endl; });

To do this, I try to use a map to hold all methods. So a class can be hold any type of functions is needed. Here I use a empty base class, and design a derived templated class to hold actual function object. When i need use the method, I can use dynamic_cast base to certain derived.

Beside, I want to assemable c++ perfect forward.

So I write the code below (now all writed by myself):

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

using namespace std;

class CallableBase {
public:
    virtual ~CallableBase() {}
};

template <class Func>
class Callable: public CallableBase {
    Func f_;
public:
    Callable(Func func): f_(func) {

    }
    template <class ...Args>
    void execute(Args&& ...args) {
        f_(std::forward<Args>(args)...);
    }
};


unordered_map<string, shared_ptr<CallableBase>> methods_;

template <class ...Args>
void execute(const std::string &name, Args&& ... args) {
    using func_t = std::function<void(Args...)>;
    auto f_ptr = dynamic_cast<Callable<func_t>*>(methods_[name].get());
    f_ptr->execute(std::forward<Args>(args)...);
}


class TestObj {
public:
    // TestObj() { cout << "Default Constructor. " << endl; }
    TestObj() { }
    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() {

    methods_["echo"] = std::make_shared<Callable<std::function<void(int s)>>>([](int s){ cout << s << endl; });
    methods_["add"] = std::make_shared<Callable<std::function<void(int,int,int&)>>>([](int a, int b, int& c){ c= a+b; });
    methods_["sub"] = std::make_shared<Callable<std::function<void(int,int,int&)>>>([](int a, int b, int& c){ c= a-b; });
    int outer = 1;
    methods_["modify_outer"] = std::make_shared<Callable<std::function<void(int,int)>>>([&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;

    return 0;
}

However, I find it can only use some build-in type, such as int, float. std::string, or TestObj will lead to zsh: segmentation fault.

I don't know what happened, and how to fixed it。。。。

Mengyu Chen
  • 137
  • 6
  • 1
    Show an example of when it causes segmentation fault. Also, show the backtrace. – kiner_shah Mar 31 '23 at 11:14
  • 1
    `methods_["echo"] = std::make_shared>>([](int s){ cout << s << endl; });` . and then call `execute("echo", "hello")` will cause segmentation fault – Mengyu Chen Mar 31 '23 at 11:16
  • 1
    you should really test the result of `dynamic_cast` – apple apple Mar 31 '23 at 11:19
  • 1
    and perfect forward doesn't apply well here, `int c; execute(verb,c);` would not call the same function as `execute(verb,1)` – apple apple Mar 31 '23 at 11:20
  • thank you for your advice! I find dynamic_cast return a nullptr ... – Mengyu Chen Mar 31 '23 at 11:32
  • 1
    consider the comment I made on this answer https://stackoverflow.com/a/75895750/4117728. Without perfect forwarding there can be trouble. WIth perfect forwarding it gets worse. Forwarding is ok, the problem is that you use the deduced types of the arguments for the cast. And they arent always what you expect – 463035818_is_not_an_ai Mar 31 '23 at 11:32
  • haha, it's also asked by me. It actually helps me a lot. After I read it, I want to do some improvements. Such as letting it support this usage: `server.Register(..., [this](){...})`. – Mengyu Chen Mar 31 '23 at 11:47

0 Answers0