检查函数指针是否被注册
Check if function pointer is registered
上下文
(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);
序列化现在很容易,因为我们可以查找函数并发出名称。同样,反序列化查找名称并发出包装在 class 中的函数工厂(这是简单的部分)。
注册工作如下:基本上对于每个函数,我将使用函数指针和名称(用于(反)序列化)调用 Register
。这里涉及一个宏和一些助手 classes 来完成管道。例如:
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 是一个静态函数,因此不是成员函数。删除 'static' 恐怖开始了...
问题
当我尝试在 MSVC++ 中编译这段代码时,我收到一个错误,告诉我函数到成员的指针不能被散列:
error C2338: The C++ Standard doesn't provide a hash for this type.
为了解决这个问题,我尝试将 fcn
转换为另一种指针类型,这似乎也是不允许的。
请注意,成员函数指针不需要是唯一的;由于 Fun
是一个模板,我只需要为每个具有相同签名的成员函数指针提供一个唯一 ID。如果 name
被注册了两次,只需抛出异常即可轻松确保其唯一性。
问题
这给我留下了一个简单的问题:什么 会 起作用?如何检查函数指针是否已注册?
我修改了你的代码,现在它只适用于一个成员函数。这只是一个例子:
// 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);
我知道这是一个糟糕的解决方案,但也许可以改进。
好的,我在这里找到了最坏情况下的答案。 Get memory address of member function?
基本上这涉及将成员函数指针转换为 (void*&)。
我们称之为 'plan Z'...它显然不可移植也不可维护。我只是将它作为 'answer' 发布在这里,因为它可能会起作用。恕我直言,任何其他答案都非常可取。
我找不到以可移植方式获取成员函数地址的方法。但我可以提出 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 建议的修复 - 请参阅我下面的实现)
为成员函数实现哈希class
您不能将指向成员函数的指针用作 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);
这有点复杂,但是你直接注册成员函数。
注意:两种方法都透明地接受虚拟成员函数。
尽管我 post 这个答案,但大部分功劳应该归功于 Mikael Kilpeläinen。尽管我没有养成接受自己的答案的习惯,但我觉得这无论如何都不是我的答案,而且是这里唯一可以绝对解决它的答案。谢谢米凯尔!
事实证明,实际上有一个非常简单的解决方案。我应该做的主要观察是,对于每种类型,没有那么多具有完全相同签名(当然包括 class 类型和参数)的函数。
第二个重要观察结果是成员函数指针可以进行相等比较。
结果出奇地简单:将函数放在一个向量中,或者让哈希码只是 return 一个常量(例如 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 作为散列值。实施起来也很容易。
上下文
(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);
序列化现在很容易,因为我们可以查找函数并发出名称。同样,反序列化查找名称并发出包装在 class 中的函数工厂(这是简单的部分)。
注册工作如下:基本上对于每个函数,我将使用函数指针和名称(用于(反)序列化)调用 Register
。这里涉及一个宏和一些助手 classes 来完成管道。例如:
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 是一个静态函数,因此不是成员函数。删除 'static' 恐怖开始了...
问题
当我尝试在 MSVC++ 中编译这段代码时,我收到一个错误,告诉我函数到成员的指针不能被散列:
error C2338: The C++ Standard doesn't provide a hash for this type.
为了解决这个问题,我尝试将 fcn
转换为另一种指针类型,这似乎也是不允许的。
请注意,成员函数指针不需要是唯一的;由于 Fun
是一个模板,我只需要为每个具有相同签名的成员函数指针提供一个唯一 ID。如果 name
被注册了两次,只需抛出异常即可轻松确保其唯一性。
问题
这给我留下了一个简单的问题:什么 会 起作用?如何检查函数指针是否已注册?
我修改了你的代码,现在它只适用于一个成员函数。这只是一个例子:
// 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);
我知道这是一个糟糕的解决方案,但也许可以改进。
好的,我在这里找到了最坏情况下的答案。 Get memory address of member function?
基本上这涉及将成员函数指针转换为 (void*&)。
我们称之为 'plan Z'...它显然不可移植也不可维护。我只是将它作为 'answer' 发布在这里,因为它可能会起作用。恕我直言,任何其他答案都非常可取。
我找不到以可移植方式获取成员函数地址的方法。但我可以提出 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 建议的修复 - 请参阅我下面的实现)
为成员函数实现哈希class
您不能将指向成员函数的指针用作 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);
这有点复杂,但是你直接注册成员函数。
注意:两种方法都透明地接受虚拟成员函数。
尽管我 post 这个答案,但大部分功劳应该归功于 Mikael Kilpeläinen。尽管我没有养成接受自己的答案的习惯,但我觉得这无论如何都不是我的答案,而且是这里唯一可以绝对解决它的答案。谢谢米凯尔!
事实证明,实际上有一个非常简单的解决方案。我应该做的主要观察是,对于每种类型,没有那么多具有完全相同签名(当然包括 class 类型和参数)的函数。
第二个重要观察结果是成员函数指针可以进行相等比较。
结果出奇地简单:将函数放在一个向量中,或者让哈希码只是 return 一个常量(例如 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 作为散列值。实施起来也很容易。