Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using a STL map of function pointers

I developed a scripting engine that has many built-in functions, so to call any function, my code just went into an if .. else if .. else if wall checking the name but I would like to develop a more efficient solution.

Should I use a hashmap with strings as keys and pointers as values? How could I do it by using an STL map?

EDIT: Another point that came into my mind: of course using a map will force the compiler not to inline functions, but my inefficient approach didn't have any overhead generated by the necessity of function calls, it just executes code.

So I wonder if the overhead generated by the function call will be any better than having an if..else chain.. otherwise I could minimize the number of comparisons by checking a character at runtime (will be longer but faster).

like image 631
Jack Avatar asked Jan 26 '10 01:01

Jack


People also ask

What is the use of map in STL?

Maps are associative containers that store elements in a mapped fashion. Each element has a key value and a mapped value. No two mapped values can have the same key values.

How is STL map implemented?

STL Map Internal Implementation: It's implemented as a self-balancing red-black tree. Probably the two most common self balancing trees are red-black tree and AVL trees.

How do you declare a function pointer in C++?

We declare the function pointer, i.e., void (*ptr)(char*). The statement ptr=printname means that we are assigning the address of printname() function to ptr. Now, we can call the printname() function by using the statement ptr(s).

Which data structure is used in the implementation of the STL map?

So hash table it is.


6 Answers

Whatever your function signatures are:

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)();
}

Note that the ScriptFunction type could be generalized to std::function</* whatever*/> so you can support any callable thing, not just exactly function pointers.

like image 79
GManNickG Avatar answered Oct 05 '22 17:10

GManNickG


In C++11 you can do something like this : This Interface needs only the return type and it takes care of everything else from the caller side.

#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;
}
like image 29
Mohit Avatar answered Oct 05 '22 16:10

Mohit


You can also use Boost.Function and Boost.Bind what even allows you, to some degree, to have map of heterogeneous functions:

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));

It can be a map of functions of compatible prototypes as well, of course.

like image 23
mloskot Avatar answered Oct 05 '22 15:10

mloskot


Above answers seem to give a complete overview, this regards only your second question:

Map element retrieval by key has O(log n) complexity. Hashmap retrieval by key has O(1) complexity + a little stuff on the side in case of collisions. So if theres a good hash function for your function names, use it. Your implementation will have a standard one. It should be fine.

But be aware, that anything below a hundred elements will not benefit all too much.

The only downside of a hash map is collision. In your case, the hashmap will be relatively static. You know the function names you support. So I advise you to create a simple test case, where you call unordered_map<...>::hash_function with all your keys to make sure that nothing collides. After that, you can forget about it.

A quick google for potential improvements on hash functions got me there:

A fiew good hash functions

Maybe, depending on your naming conventions, you can improve on some aspects of the function.

like image 23
AndreasT Avatar answered Oct 05 '22 15:10

AndreasT


Well, you can use any_map to store functions with different signatures (but calling it will be messy) and you can use int_map to call functions with a specific signature (looks nicer).

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;
}
like image 31
Jacob Avatar answered Oct 05 '22 15:10

Jacob


I've managed to modify the example from Mohit to work on member function pointers:

#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;
}
like image 28
Ia Rigby Avatar answered Oct 05 '22 16:10

Ia Rigby