wake-up-neo.com

Verwenden einer STL-Karte von Funktionszeigern

Ich habe eine Skripting-Engine entwickelt, die viele integrierte Funktionen hat. Um eine Funktion aufzurufen, wurde mein Code in eine if .. else if .. else if-Wall geschrieben, die den Namen überprüft, aber ich möchte eine effizientere Lösung entwickeln.

Sollte ich eine hashmap mit Strings als Schlüssel und Zeiger als Werte verwenden? Wie kann ich dies mit einer STL-Karte tun? 

EDIT: Ein weiterer Punkt, der mir in den Sinn kam: Natürlich wird der Compiler bei Verwendung einer Map nicht dazu gezwungen, Funktionen inline zu integrieren, aber mein ineffizienter Ansatz hat keinen zusätzlichen Aufwand durch die Notwendigkeit von Funktionsaufrufen , es führt nur Code aus.

Ich frage mich also, ob der durch den Funktionsaufruf generierte Overhead besser ist als eine if..else-Kette. Ansonsten könnte ich die Anzahl der Vergleiche minimieren, indem ein Zeichen zur Laufzeit geprüft wird (wird länger, aber schneller).

33
Jack

Was auch immer Ihre Funktionssignaturen sind:

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

Beachten Sie, dass der Typ ScriptFunction auf std::function</* whatever*/> verallgemeinert werden kann, sodass Sie jedes aufrufbare Element unterstützen können, nicht nur genau Funktionszeiger.

39
GManNickG

Sie können auch Boost.Function und Boost.Bind verwenden, wodurch Sie sogar bis zu einem gewissen Grad eine Karte der heterogenen - Funktionen haben:

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

Natürlich kann es auch eine Funktionskarte kompatibler Prototypen sein.

15
mloskot

In C++ 11 können Sie Folgendes tun: Diese Schnittstelle benötigt nur den Rückgabetyp und kümmert sich um alles andere auf Anruferseite.

#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

Die obigen Antworten scheinen einen vollständigen Überblick zu geben, dies betrifft nur Ihre zweite Frage:

Das Abrufen von Kartenelementen mit dem Schlüssel hat eine O (log n) -Komplexität. Der Hashmap-Abruf per Schlüssel hat O(1) -Komplexität + ein kleines Zeug auf der Seite bei Kollisionen. Wenn es also eine gute Hash-Funktion für Ihre Funktionsnamen gibt, verwenden Sie sie. Ihre Implementierung wird standardisiert sein. Es sollte okay sein.

Beachten Sie jedoch, dass alles, was unter hundert Elementen liegt, nicht allzu viel nützen wird.

Der einzige Nachteil einer Hash-Karte ist die Kollision. In Ihrem Fall ist die Hashmap relativ statisch. Sie kennen die Funktionsnamen, die Sie unterstützen. Daher empfehle ich Ihnen, einen einfachen Testfall zu erstellen, in dem Sie unordered_map <...> :: hash_function mit allen Ihren Schlüsseln aufrufen, um sicherzustellen, dass nichts kollidiert. Danach können Sie es vergessen.

Eine schnelle Suche nach möglichen Verbesserungen der Hash-Funktionen brachte mich dort hin:

Eine gute Hash-Funktion

Abhängig von Ihren Namenskonventionen können Sie möglicherweise einige Aspekte der Funktion verbessern.

7
AndreasT

Nun, Sie können any_map verwenden, um Funktionen mit unterschiedlichen Signaturen zu speichern (der Aufruf ist jedoch unordentlich) und Sie können int_map verwenden, um Funktionen mit einer bestimmten Signatur aufzurufen (sieht schöner aus).

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

Ich habe versucht, die zweite Antwort mit C++ 11 zu verwenden. Ich musste die letzte Zeile ändern Von:
(* iter) ();
zu:
(* iter-> second) ();

so lautet der Code jetzt:

    #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

Ich habe es geschafft, das -Beispiel von Mohit zu modifizieren, um an Elementfunktionszeigern zu arbeiten:

#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