2010-01-26 6 views
31

J'ai développé un moteur de script qui a de nombreuses fonctions intégrées, donc pour appeler n'importe quelle fonction, mon code est entré dans un mur if .. else if .. else if en vérifiant le nom mais je voudrais développer une solution plus efficace .Utilisation d'une carte STL de pointeurs de fonction

Dois-je utiliser un hashmap avec des chaînes comme clés et des pointeurs comme valeurs? Comment pourrais-je le faire en utilisant une carte STL?

EDIT: Un autre point qui est venu dans mon esprit: bien sûr en utilisant une carte forcera le compilateur de ne pas les fonctions inline, mais mon approche inefficace n'avait pas de frais généraux générés par la nécessité des appels de fonction, il juste exécute le code. Donc je me demande si le surcoût généré par l'appel de la fonction sera meilleur que d'avoir une chaîne if..else .. sinon je pourrais minimiser le nombre de comparaisons en vérifiant un caractère à l'exécution (sera plus long mais plus rapide).

Répondre

36

Quels que soient vos signatures de fonction sont:

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

Notez que le type ScriptFunction pourrait être généralisé à std::function</* whatever*/> afin que vous puissiez soutenir quelque chose appelable, non seulement exactement les pointeurs de fonction.

+1

De même, il n'est pas vraiment nécessaire d'utiliser une vraie table de hachage comme 'unordered_map'. Il n'y aura pas autant d'éléments qu'une table de hachage apporterait des avantages de performance, je ne serais même pas surpris si 'map' était plus rapide dans ce cas. – sth

+3

En fait, j'ai fait des choses similaires et 'unordered_map' était * beaucoup * plus rapide. Je n'avais qu'environ 10 000 choses, et j'ai profilé 'map' et' unordered_map'. – GManNickG

+1

Je m'attendrais '' "beaucoup de fonctions intégrées" << 10.000'. Hasmap dans le cas de l'OP a l'avantage clair d'être "vrai O (1)" car il n'a pas à croître, et un hachage sans collision pourrait être construit pour les cordes. Je doute que cela fasse une différence * significative * par rapport à une carte pour seulement quelques centaines d'objets. – peterchen

3

Eh bien, vous pouvez utiliser any_map pour stocker des fonctions avec différentes signatures (mais l'appeler sera salissant) et vous pouvez utiliser int_map pour appeler des fonctions avec une signature spécifique (plus agréable).

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; 
} 
+1

En fait, je trouve cela très utile. Vous pouvez écrire vos propres fonctions qui enveloppent la réinterprétation (c'est-à-dire float my_b() {return reinterpret .....} –

+3

Avez-vous vraiment écrit 'void main' dans un programme C++? –

+3

@ LB--: Pourquoi? Oh, attendez .. Oh, attendez .. 140 rep – Jacob

15

Vous pouvez également utiliser Boost.Function et Boost.Bind ce qui vous permet même, dans une certaine mesure, d'avoir carte de hétérogènes fonctions:

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

Il peut être une carte de fonctions de prototypes compatibles comme Oui bien sur.

+0

Cela n'a pas fonctionné pour moi J'ai une erreur de compilation 'boost :: function': trop d'arguments de template – excray

+0

@ vivek-g, il y a beaucoup de problèmes possibles , la version du compilateur, manquant inclut, etc. il ne compiler et exécuter pour moi ainsi que pour CodePad: http://codepad.org/ciKTrh2r – mloskot

+1

@mloskot, merci par exemple impressionnant –

7

réponses ci-dessus semblent donner un aperçu complet, ce qui concerne seulement votre deuxième question:

récupération des éléments de la carte par clé a O (log n) complexité. La récupération de Hashmap par clé a O (1) complexité + un petit truc sur le côté en cas de collisions. Donc, s'il existe une bonne fonction de hachage pour vos noms de fonctions, utilisez-la. Votre implémentation aura un standard. Ça devrait aller. Mais sachez que tout ce qui est en dessous d'une centaine d'éléments ne profitera pas à tous.

Le seul inconvénient d'une carte de hachage est la collision. Dans votre cas, le hashmap sera relativement statique. Vous connaissez les noms de fonctions que vous supportez. Je vous conseille donc de créer un cas de test simple, où vous appelez unordered_map < ...> :: hash_function avec toutes vos clés pour vous assurer que rien ne se heurte. Après cela, vous pouvez l'oublier.

Un rapide Google pour les améliorations possibles sur les fonctions de hachage me arrivé là:

A fiew good hash functions

Peut-être, en fonction de vos conventions de nommage, vous pouvez améliorer certains aspects de la fonction.

0

J'ai essayé d'utiliser la deuxième réponse avec C++ 11. J'ai dû changer la dernière ligne de:
(* iter)();
à:
(* iter-> second)();

le code est donc maintenant:

#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; 
    } 
7

En C++ 11 vous pouvez faire quelque chose comme ceci: Cette interface a besoin que le type de retour et il prend soin de tout le reste du côté de l'appelant.

#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; 
} 
+0

Ceci est l'or est-il possible!. ajouter des fonctions membres au mélange? Peut-être en le moulant à un type de non-membre à un moment donné? Merci – LRP