使用功能指针的STL映射

问题描述 投票:41回答:8

我开发了具有许多内置功能的脚本引擎,因此要调用任何功能,我的代码只是进入if .. else if .. else if墙检查名称,但我想开发一个更有效的解决方案。

我应该使用hashmap,将字符串作为键,将指针作为值吗?如何使用STL映射来做到这一点?

编辑:我想到的另一点是:当然,使用映射将迫使编译器不内联函数,但是我效率低下的方法没有因函数调用的必要而产生任何开销,它只是执行代码。

因此,我想知道函数调用所产生的开销是否比具有if..else链更好。否则,我可以通过在运行时检查字符来将比较次数减至最少(会更长但更快)。

c++ stl map function-pointers
8个回答
48
投票

无论您的功能签名是什么:

typedef void (*ScriptFunction)(void); // function pointer type
typedef std::unordered_map<std::string, ScriptFunction> script_map;

// ...

void some_function()
{
}

// ...

script_map m;
m.emplace("blah", &some_function);

// ...

void call_script(const std::string& pFunction)
{
    auto iter = m.find(pFunction);
    if (iter == m.end())
    {
        // not found
    }

    (*iter->second)();
}

请注意,ScriptFunction类型可以泛化为std::function</* whatever*/>,因此您可以支持任何可调用的东西,而不仅仅是功能指针。


16
投票

您还可以使用Boost.FunctionBoost.Bind在某种程度上甚至允许您具有异构函数的映射:

typedef boost::function<void, void> fun_t;
typedef std::map<std::string, fun_t> funs_t;
funs_t f;

void foo() {}
void goo(std::string& p) {}
void bar(int& p) {}

f["foo"] = foo;
f["goo"] = boost::bind(goo, "I am goo");
f["bar"] = boost::bind(bar, int(17));

当然,它也可以是兼容原型的功能图。


15
投票

在C ++ 11中,您可以执行以下操作:该接口仅需要返回类型,并且可以处理来自调用方的所有其他操作。

#include <string>
#include <iostream>
#include <map>
#include <vector>
#include <typeinfo>
#include <typeindex>
#include <cassert>

void fun1(void){
    std::cout<<"inside fun1\n";
}

int fun2(){
    std::cout<<"inside fun2\n";
    return 2;
}

int fun3(int a){
    std::cout<<"inside fun3\n";
    return a;
}

std::vector<int> fun4(){
    std::cout<<"inside fun4\n";
    std::vector<int> v(4,100);
    return v;
}

// every function pointer will be stored as this type
typedef void (*voidFunctionType)(void); 

struct Interface{

    std::map<std::string,std::pair<voidFunctionType,std::type_index>> m1;

    template<typename T>
    void insert(std::string s1, T f1){
        auto tt = std::type_index(typeid(f1));
        m1.insert(std::make_pair(s1,
                        std::make_pair((voidFunctionType)f1,tt)));
    }

    template<typename T,typename... Args>
    T searchAndCall(std::string s1, Args&&... args){
        auto mapIter = m1.find(s1);
        /*chk if not end*/
        auto mapVal = mapIter->second;

        // auto typeCastedFun = reinterpret_cast<T(*)(Args ...)>(mapVal.first); 
        auto typeCastedFun = (T(*)(Args ...))(mapVal.first); 

        //compare the types is equal or not
        assert(mapVal.second == std::type_index(typeid(typeCastedFun)));
        return typeCastedFun(std::forward<Args>(args)...);
    }
};

int main(){
    Interface a1;
    a1.insert("fun1",fun1);
    a1.insert("fun2",fun2);
    a1.insert("fun3",fun3);
    a1.insert("fun4",fun4);

    a1.searchAndCall<void>("fun1");
    int retVal = a1.searchAndCall<int>("fun3",2);
    a1.searchAndCall<int>("fun2");
    auto temp = a1.searchAndCall<std::vector<int>>("fun4");

    return 0;
}

7
投票

以上答案似乎给出了完整的概述,这仅涉及您的第二个问题:

通过键检索地图元素具有O(log n)复杂度。通过键进行哈希映射检索具有O(1)复杂度,并且在发生冲突的情况下会在侧面产生一些麻烦。因此,如果您的函数名称具有良好的哈希函数,请使用它。您的实现将有一个标准的实现。应该没问题。

但是要知道,任何少于一百个元素都不会带来太多好处。

散列图的唯一缺点是碰撞。在您的情况下,哈希图将相对静态。您知道所支持的功能名称。因此,我建议您创建一个简单的测试用例,在其中用所有键调用unordered_map <...> :: hash_function以确保没有碰撞。之后,您就可以忘记它。

快速谷歌对哈希函数的潜在改进使我到达那里:

A fiew good hash functions

也许,根据您的命名约定,您可以在功能的某些方面进行改进。


4
投票

嗯,您可以使用any_map存储具有不同签名的函数(但是调用它会很麻烦),并且可以使用int_map调用具有特定签名的函数(看起来更好)。

int FuncA()
{
    return 1;
}

float FuncB()
{
    return 2;
}


int main()
{
    // Int map
    map<string,int(*)()> int_map;
    int_map["A"] = FuncA;
    // Call it
    cout<<int_map["A"]()<<endl;

    // Add it to your map
    map<string, void(*)> any_map;
    any_map["A"] = FuncA;
    any_map["B"] = FuncB;

    // Call
    cout<<reinterpret_cast<float(*)()>(any_map["B"])()<<endl;
}

0
投票

我试图在c ++ 11中使用第二个答案。我不得不改变最后一行从:(* iter)();至:(* iter-> second)();

所以代码现在是:

    #include <map>

    typedef void (*ScriptFunction)(void); // function pointer type
    typedef std::map<std::string, ScriptFunction> script_map;

    // ...

    void some_function(void)
    {
    }
    script_map m;

    void call_script(const std::string& pFunction)
    {
        script_map::const_iterator iter = m.find(pFunction);
        if (iter == m.end())
        {
            // not found
        }
        (*iter->second)();
    }

    int main(int argc, const char * argv[])
    {
        //..
        m.insert(std::make_pair("blah", &some_function));

        call_script("blah");
        //..
        return 0;
    }

0
投票

我设法修改了example from Mohit以在成员函数指针上工作:

#include <string>
#include <iostream>
#include <map>
#include <vector>
#include <typeinfo>
#include <typeindex>
#include <cassert>


template <typename A>
using voidFunctionType = void (A::*)(void);

template <typename A>
struct Interface{

    std::map<std::string,std::pair<voidFunctionType<A>,std::type_index>> m1;

    template<typename T>
    void insert(std::string s1, T f1){
        auto tt = std::type_index(typeid(f1));
        m1.insert(std::make_pair(s1,
                        std::make_pair((voidFunctionType<A>)f1,tt)));
    }

    template<typename T,typename... Args>
    T searchAndCall(A a, std::string s1, Args&&... args){
        auto mapIter = m1.find(s1);
        auto mapVal = mapIter->second;  

        auto typeCastedFun = (T(A::*)(Args ...))(mapVal.first); 

        assert(mapVal.second == std::type_index(typeid(typeCastedFun)));
        return (a.*typeCastedFun)(std::forward<Args>(args)...);
    }
};

class someclass {
    public:
        void fun1(void);
        int fun2();
        int fun3(int a);
        std::vector<int> fun4();
};

void someclass::fun1(void){
    std::cout<<"inside fun1\n";
}

int someclass::fun2(){
    std::cout<<"inside fun2\n";
    return 2;
}

int someclass::fun3(int a){
    std::cout<<"inside fun3\n";
    return a;
}

std::vector<int> someclass::fun4(){
    std::cout<<"inside fun4\n";
    std::vector<int> v(4,100);
    return v;
}

int main(){
    Interface<someclass> a1;
    a1.insert("fun3",&someclass::fun3);
     someclass s;
    int retVal = a1.searchAndCall<int>(s, "fun3", 3);
    return 0;
}

0
投票

我尝试将示例修改为接口作为类的成员,并将调用包装到searchAndCall,但是内部fun1堆栈已损坏,a和b变量错误实际上,它在断言assert(mapVal.second == std::type_index(typeid(typeCastedFun)));上失败了,因为类型不同如何正确编写包装器?

template <typename A>
using voidFunctionType = void (A::*)(void);

template <typename A>
struct Interface {

    std::map<std::string, std::pair<voidFunctionType<A>, std::type_index>> m1;

    template<typename T>
    void insert(std::string s1, T f1) {
        auto tt = std::type_index(typeid(f1));
        m1.insert(std::make_pair(s1,
            std::make_pair((voidFunctionType<A>)f1, tt)));
    }

    template<typename T, typename... Args>
    T searchAndCall(A* a, std::string s1, Args&&... args) {
        auto mapIter = m1.find(s1);
        auto mapVal = mapIter->second;

        auto typeCastedFun = (T(A::*)(Args ...))(mapVal.first);
        auto type = std::type_index(typeid(typeCastedFun));
        assert(mapVal.second == std::type_index(typeid(typeCastedFun)));
        return (a->*typeCastedFun)(std::forward<Args>(args)...);
    }
};
class someclass2
{
    Interface<someclass2> a1;
public:
    someclass2()
    {
        a1.insert("fun1", &someclass2::fun1);
    }
    int fun1(int a, int b)
    {
        return a + b;
    }
    void masterfunction(int a, int b)
    {
        int result =  a1.searchAndCall<int>(this, "fun1", a,b);
        std::cout << "Result " << result << std::endl;
    }
};
int main()
{
    someclass2 s1;
    s1.masterfunction(1, 2);
    return 0;
}
© www.soinside.com 2019 - 2024. All rights reserved.