检查函数指针是否已注册

时间:2015-04-22 13:02:53

标签: c++ c++11 function-pointers member-function-pointers

上下文

(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); 是唯一的。

问题

这给我留下了一个简单的问题:的工作原理是什么?如何检查函数指针是否已注册?

4 个答案:

答案 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'......它显然不便携也不可维护。我只是将它作为'回答'发布在这里,因为它可能会起作用。任何其他答案都是非常可取的。