web-dev-qa-db-fra.com

Utilisation d'une carte STL de pointeurs de fonction

J'ai développé un moteur de script doté de nombreuses fonctions intégrées. Par conséquent, pour appeler n'importe quelle fonction, mon code est entré dans un mur if .. else if .. else if en vérifiant le nom, mais j'aimerais développer une solution plus efficace.

Devrais-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 m’est venu à l’esprit: bien sûr, utiliser une carte forcera le compilateur à ne pas utiliser les fonctions inline, mais mon approche inefficace n’a généré aucune surcharge due à la nécessité d’appels de fonction , il n’exécute que du code.

Je me demande donc si la surcharge générée par l'appel de fonction sera meilleure 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 (ce sera plus long mais plus rapide).

33
Jack

Quelles que soient vos signatures de fonction:

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 peut être généralisé à std::function</* whatever*/> afin que vous puissiez prendre en charge tout ce qui peut être appelé, pas seulement les pointeurs de fonction.

39
GManNickG

Vous pouvez également utiliser Boost.Function et Boost.Bind what vous permet même, dans une certaine mesure, d’avoir une carte des fonctions hétérogène:

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

Ce peut être bien sûr une carte des fonctions de prototypes compatibles.

15
mloskot

En C++ 11, vous pouvez faire quelque chose comme ceci: Cette interface n'a besoin que du type de retour et s'occupe 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;
}
12
Mohit

Les réponses ci-dessus semblent donner un aperçu complet, cela ne concerne que votre deuxième question:

La récupération des éléments de carte par clé présente une complexité O (log n). La récupération de la table de hachage par clé présente une complexité O(1) + un petit côté sur le côté en cas de collision. Donc, s'il y a une bonne fonction de hachage pour les noms de vos fonctions, utilisez-la. Votre implémentation aura un standard. Ça devrait bien se passer.

Mais soyez conscient que rien en dessous de cent éléments ne bénéficiera trop.

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 fonction que vous prenez en charge. Je vous conseille donc de créer un scénario de test simple, dans lequel 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 des améliorations potentielles sur les fonctions de hachage m'a amené là:

Un peu de bonnes fonctions de hachage

Selon vos conventions de dénomination, vous pourrez peut-être améliorer certains aspects de la fonction.

7
AndreasT

Eh bien, vous pouvez utiliser any_map pour stocker des fonctions avec différentes signatures (mais l’appel sera désordonné) et vous pouvez utiliser int_map pour appeler des fonctions avec une signature spécifique (plus esthétique).

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;
}
3
Jacob

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

alors le code est 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;
    }
0
EckhardN

J'ai réussi à modifier l'exemple de Mohit pour qu'il fonctionne sur les pointeurs de fonction membre:

#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
Ia Rigby