检查函数指针是否被注册

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 作为散列值。实施起来也很容易。