上下文
(C ++ 11)作为序列化代码中的安全措施的一部分,我想检查函数指针是否已知。 (否则,反序列化机制可能会失败)。
这是一段(简化的)代码,用于说明机制:
template <typename Fun>
struct FunctionCallRegistry
{
static std::unordered_map<Fun, std::string>& FunctionMap()
{
static std::unordered_map<Fun, std::string> map;
return map;
}
static void Register(Fun function, std::string name)
{
auto& map = FunctionMap();
auto it = map.find(function);
if (it == map.end())
{
map.insert(std::pair<Fun, std::string>(function, name));
// other code follows...
}
}
static void Ensure(Fun function)
{
auto& map = FunctionMap();
auto it = map.find(function);
if (it == map.end())
{
throw std::exception("Function not found.");
}
}
};
// Registration code:
struct FunctionCallRegistryInitializer
{
template <typename Ret, typename... Args>
explicit FunctionCallRegistryInitializer(Ret(*function)(Args...),
const char* name)
{
FunctionCallRegistry<Ret(*)(Args...)>::Register(function, name);
}
};
#define RegisterFunction(fcn) FunctionCallRegistryInitializer fcn_reg_##__COUNTER__(&fcn, #fcn);
序列化现在很简单,因为我们可以查找函数并发出名称。类似地,反序列化查找名称并发出包含在类中的函数工厂(这是简单的部分)。
注册的工作原理如下:基本上对于每个函数,我将使用函数指针和名称(用于(反)序列化)调用Register
。这里涉及一个宏,还有一些辅助类来管理。 E.g:
struct Testje
{
static int test(int lhs, int rhs) {
std::cout << lhs << " + " << rhs << std::endl;
return lhs + rhs;
}
};
RegisterFunction(Testje::test);
这里Fun
的类型是f.ex. void (*fcn)(int, int)
。这很好用,因为test是一个静态函数,因此不是成员函数。删除&#39;静态&#39;而恐怖开始......
问题
当我尝试在MSVC ++中编译此代码时,我收到一个错误,告诉我函数到成员的指针不能被散列:
error C2338: The C++ Standard doesn't provide a hash for this type.
为了解决这个问题,我尝试将fcn
转换为另一种指针类型,这似乎也不允许。
请注意,成员函数指针不是唯一的;我只需要一个具有相同签名的每个成员函数指针的唯一ID,因为Fun
是一个模板。如果name
已经注册两次,只需抛出异常就可以很容易地确保Intent start_service = new Intent(MyTrip.this, AppLocationService.class);
startService(start_service);
是唯一的。
问题
这给我留下了一个简单的问题:将的工作原理是什么?如何检查函数指针是否已注册?
答案 0 :(得分:1)
我修改了你的代码,现在它仅适用于一个成员函数。这只是一个例子:
// Registration code:
struct FunctionCallRegistryInitializer
{
// Here I changed Ret(*function) to Ret(T::*function)
template <typename T, typename Ret, typename... Args>
explicit FunctionCallRegistryInitializer(Ret(T::*function)(Args...),
const char* name)
{
FunctionCallRegistry<Ret(T::*)(Args...)>::Register(function, name);
}
};
// We need it for use use member-function test like key value in map
namespace std {
template <>
struct hash<decltype(&Testje::test)>
{
size_t operator()(decltype(&Testje::test) x) const
{
return typeid(x).hash_code();
}
};
}
RegisterFunction(Testje::test);
我们可以检查成员函数是否已注册:
FunctionCallRegistry<decltype(&Testje::test)>::Ensure(&Testje::test);
这是一个可怕的解决方案,我知道,但可能会有所改善。
答案 1 :(得分:1)
我找不到以便携方式获取成员函数地址的方法。但我可以提出2个解决方法。
我们都知道成员函数等同于将其对象作为第一个参数的函数。如果我们有:
struct Testje
{
int test(int lhs, int rhs) {
std::cout << lhs << " + " << rhs << std::endl;
return lhs + rhs;
}
};
很容易用
包裹它template<class T, int(T::*mf)(int, int)>
int wrapper(T& obj, int lhs, int rhs) {
return (obj.*mf)(lhs, rhs);
}
...
FunctionCallRegistry<int (*)(Testje&, int, int)> reg;
reg.Register(&(wrapper<Testje, &Testje::test>), "test");
使用C ++ 11编译器编译并运行良好(前提是您应用了Quentin和dyp建议的修补程序 - 请参阅下面的实现)
您不能将指向成员函数的指针用作unordered_map
中的键,因为它无法直接进行哈希处理。但是你可以按照Roland W的建议实现哈希:
class Testje{
public:
int test(int lhs, int rhs) {
std::cout << lhs << " + " << rhs << std::endl;
return lhs + rhs;
}
static int myhash(int (Testje::*f)(int, int) f) {
// for each and every member function with that signature return a different int
if (f == &Testje::test) {
return 0;
}
return -1;
}
class Hash {
public:
size_t operator( )(int (Testje::*f)(int, int) f) const {
return myhash(f);
}
};
};
然后您只需要更改FunctionCallRegistry
以使用新的哈希函数:
template <typename Fun, class Hash = std::hash<Fun>>
struct FunctionCallRegistry
{
// not static to allow multiple registries
std::unordered_map<Fun, std::string, Hash> _map;
std::unordered_map<Fun, std::string, Hash>& FunctionMap() // returns a reference ...
{
return _map;
}
void Register(Fun function, std::string name)
{
std::unordered_map<Fun, std::string, Hash>& map = FunctionMap();
auto it = map.find(function);
if (it == map.end())
{
map.insert(std::pair<Fun, std::string>(function, name));
// other code follows...
}
}
void Ensure(Fun function)
{
std::unordered_map<Fun, std::string, Hash>& map = FunctionMap();
auto it = map.find(function);
if (it == map.end())
{
throw std::exception();
}
}
};
然后您可以将其用于:
FunctionCallRegistry<int (Testje::*)(int, int), Testje::Hash> reg;
reg.Register(&Testje::test, "test");
reg.Ensure(&Testje::test);
这有点复杂但你直接注册成员函数。
注意:两种方法都透明地接受虚拟成员函数。
答案 2 :(得分:1)
尽管我发布了这个答案,但大部分学分应该归MikaelKilpeläinen所有。虽然我不习惯接受我自己的答案,但我觉得这不是我的答案,而且这里唯一一个绝对解决它的人。谢谢Mikael!
事实证明,实际上有一个非常简单的解决方案。我应该做的主要观察是,对于每种类型,没有那么多具有完全相同签名的函数(当然包括类类型和参数)。
第二个重要的观察是成员函数指针可以进行相等比较。
结果非常简单:将函数放在向量中或让哈希代码只返回一个常量(例如1)。总结一下:
template <typename Fun>
struct FunctionCallFactory
{
private:
static std::vector<std::pair<Fun, std::string>>& FunctionMap()
{
static std::vector<std::pair<Fun, std::string>> map;
return map;
}
public:
static void Register(Fun function, std::string name)
{
auto& map = FunctionMap();
for (auto& it : FunctionMap())
{
if (it.first == function)
{
throw std::exception("Duplicate registration of function. Not allowed.");
}
}
map.push_back(std::pair<Fun, std::string>(function, name));
}
// etc.
};
请注意,对于每个Fun
,将创建一个单独的实例,这样可以避免在搜索过程中发生大多数冲突。因此,线性扫描将起作用。
如果要使用哈希并且不想将Fun
放在模板中,请使用函数的typeid作为哈希值。实现这一点也很容易。
答案 3 :(得分:0)
好的,我在这里找到了最坏情况的答案。 Get memory address of member function?
基本上这涉及将成员函数指针强制转换为(void *&amp;)。
我们称之为'计划Z'......它显然不便携也不可维护。我只是将它作为'回答'发布在这里,因为它可能会起作用。任何其他答案都是非常可取的。