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).
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.
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.
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).
So hash table it is.
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.
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;
}
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.
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.
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;
}
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With