我已经实现了一个名为
MethodMap
的类,它允许我存储类的成员函数指针并在运行时使用键字符串调用它们。成员函数可以接受任何参数,也可以不接受任何参数。类看起来像这样:
template <typename T, typename... Args>
class MethodMap {
private:
std::unordered_map<std::string, std::function<void(T*, Args...)>> method_map;
public:
void Insert(const std::string& key, void (T::* method)(Args...)) {
method_map[key] = [method](T* obj, Args... args) { (obj->*method)(args...); };
}
void Call(const std::string& key, T* instance, Args&&... methodArgs) const {
auto it = method_map.find(key);
if (it != method_map.end()) {
auto& func = it->second;
// use tuple to store and forward the arguments
std::tuple<Args...> arg_tuple(std::forward<Args>(methodArgs)...);
std::apply(func, std::tuple_cat(std::make_tuple(instance), arg_tuple));
return;
}
std::cerr << "Error: method '" << key << "' not found" << std::endl;
}
};
Insert
方法插入一个指向map的成员函数指针,Call
方法用给定的键和参数调用成员函数。
它运行良好,但我意识到我需要为每个采用不同参数的成员函数指针创建一个不同的
MethodMap
实例。例如,如果我有以下成员函数:
class MyClass {
public:
void Method1(int x);
void Method2(double d);
void Method3(int x, const std::string& s);
void Method4();
};
我需要为每个成员函数指针创建一个不同的
MethodMap
实例,因为它们有不同的参数列表。例如:
MethodMap<MyClass> methodmap;
MyClass myClass;
methodmap.Insert("key", &MyClass::Method4);
methodmap.Call("key", &myClass);
MethodMap<MyClass, int> methodmapWithParameters;
methodmapWithParameters.Insert("key", &MyClass::Method1);
methodmapWithParameters.Call("key", &myClass, 1);
有没有办法用一个
MethodMap
的实例来处理这个问题?
我确实遇到过类似的问题,但是在所有这些中给出的参数总是相同的,我自己很难概括。
你可以做的是拥有一个虚拟的类
member_base_ptr
,它可以作为指针存储在你的地图中(理想情况下是某种托管指针,以便它被正确释放),你可以从中扩展一个member_ptr
与你需要的类型。因为你查找那个member_base_ptr
并尝试对那个dynamic_cast
做一个member_ptr
,如果成功,你就可以将呼叫转发给那个。
这里是那个想法的粗略草稿,但我没有花太多时间思考代码中的所有内容,请验证所有内容是否真正有效并且不会导致未定义的行为。
#include <iostream>
#include <functional>
#include <memory>
#include <map>
struct Test {
int test1(float i){
std::cout << "test1" << "\n";
return 10;
}
int test2(std::string s){
std::cout << "test1" << "\n";
return 20;
}
};
struct member_base_ptr {
virtual ~member_base_ptr() = default;
};
template <typename T, typename RT, typename... Args>
struct member_ptr: public member_base_ptr {
std::function<RT(T*, Args...)> m_ptr;
member_ptr(RT (T::* method)(Args...)) {
m_ptr = [method](T* obj, Args... args) { return (obj->*method)(args...); };
}
RT call(T* instance, Args&&... methodArgs) const {
return m_ptr(instance, std::forward<Args>(methodArgs)...);
}
};
struct method_map {
std::map<std::string, std::unique_ptr<member_base_ptr>> m_ptrs;
void insert(std::string key, auto type) {
std::unique_ptr<member_base_ptr> ptr = std::make_unique<decltype(member_ptr(type))>(type);
m_ptrs.insert(std::make_pair(key, std::move(ptr)));
}
template <typename RT, typename T, typename... Args>
RT call(const std::string& key, T* instance, Args&&... methodArgs) const {
auto it = m_ptrs.find(key);
if(it != m_ptrs.end()) {
member_base_ptr *base_ptr = it->second.get();
auto test = dynamic_cast<member_ptr<T, RT, Args...> *>(base_ptr);
if( test == nullptr ) {
throw std::runtime_error("casting failed");
}
return test->call(instance, std::forward<Args>(methodArgs)...);
}
throw std::runtime_error("not found");
}
};
int main()
{
Test t;
method_map map;
map.insert("test1", &Test::test1);
map.insert("test2", &Test::test2);
std::cout << map.call<int>("test1", &t, 1.f) << "\n";
std::cout << map.call<int>("test2", &t, std::string("test")) << "\n";
return 0;
}
由于另一个答案使用了
dynamic_cast
,我宁愿避免这种情况,所以我向您展示了一个没有它的替代方案。想法是在地图getter模板成员函数中有一个static
;这将 this
映射到 name
到 function
。然后你的成员函数将有模板参数而不是你的class
:
#include <iostream>
#include <string>
#include <functional>
#include <unordered_map>
class MethodMap {
private:
public:
template <typename T, typename... Args>
std::unordered_map<std::string, std::function<void(T*, Args...)>>& get_methodmap() const
{
static std::unordered_map<const MethodMap*, std::unordered_map<std::string, std::function<void(T*, Args...)>>> this2name2method;
return this2name2method[this];
}
template <typename T, typename... Args>
void Insert(const std::string& key, void (T::* method)(Args...)) {
get_methodmap<T, Args...>()[key] = [method](T* obj, Args... args) { (obj->*method)(args...); };
}
template <typename T, typename... Args>
void Call(const std::string& key, T* instance, Args&&... methodArgs) const {
auto&& method_map = get_methodmap<T, Args...>();
auto it = method_map.find(key);
if (it != method_map.end()) {
auto& func = it->second;
// use tuple to store and forward the arguments
std::tuple<Args...> arg_tuple(std::forward<Args>(methodArgs)...);
std::apply(func, std::tuple_cat(std::make_tuple(instance), arg_tuple));
return;
}
std::cerr << "Error: method '" << key << "' not found" << std::endl;
}
};
class MyClass {
public:
void Method1(int x) {}
void Method2(double d) {}
void Method3(int x, const std::string& s) {}
void Method4() {}
};
int main()
{
MethodMap methodmap;
MyClass myClass;
methodmap.Insert("key", &MyClass::Method4);
methodmap.Call("key", &myClass);
methodmap.Insert("key", &MyClass::Method1);
methodmap.Call("key", &myClass, 1);
}