Korzystanie z mapy STL wskaźników funkcji

Opracowałem silnik skryptowy, który ma wiele wbudowanych funkcji, więc aby wywołać dowolną funkcję, Mój kod po prostu wszedł do ściany if .. else if .. else if sprawdzając nazwę, ale chciałbym opracować bardziej wydajne rozwiązanie.

Czy powinienem używać hashmap z łańcuchami jako kluczami i wskaźnikami jako wartościami? Jak mogę to zrobić używając mapy STL?

Edytuj : Kolejny punkt, który przyszedł mi do głowy: oczywiście użycie mapy zmusi kompilator nie do funkcji inline, ale mój nieefektywny approach nie miał żadnych kosztów generowanych przez konieczność wywołania funkcji, po prostu wykonuje kod.

Więc zastanawiam się, czy narzut generowany przez wywołanie funkcji będzie lepszy niż posiadanie if..else łańcucha.. w przeciwnym razie mógłbym zminimalizować liczbę porównań, sprawdzając znak w czasie wykonywania (będzie dłuższy, ale szybszy).

Author: RustyTheBoyRobot, 2010-01-26

6 answers

Niezależnie od sygnatury funkcji:

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

Zauważ, że typ ScriptFunction może być uogólniony na std::function</* whatever*/>, więc możesz obsługiwać dowolne wywoływalne rzeczy, a nie tylko Wskaźniki funkcji.

 37
Author: GManNickG,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-29 17:46:12

Możesz również użyć Boost.Function and Boost.Bind co pozwala nawet do pewnego stopnia mieć mapę heterogenicznych funkcji:

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

Może to być oczywiście Mapa funkcji kompatybilnych prototypów.

 15
Author: mloskot,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-26 01:40:28

W C++11 możesz zrobić coś takiego : Ten interfejs wymaga tylko typu powrotu i zajmuje się wszystkim innym od strony wywołującej.

#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;
}
 11
Author: Mohit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-24 19:28:56

Powyższe odpowiedzi wydają się dać pełny przegląd, dotyczy to tylko drugiego pytania:

Pobieranie elementu mapy za pomocą klucza ma złożoność O (log n). Pobieranie Hashmap za pomocą klucza ma o (1) złożoność + trochę rzeczy na boku w przypadku kolizji. Więc jeśli istnieje dobra funkcja hash dla nazw funkcji, użyj jej. Twoja implementacja będzie miała standardowy. Powinno być dobrze.

Ale należy pamiętać, że wszystko poniżej stu elementów nie przyniesie zbyt wiele korzyści.

The jedynym minusem mapy hash jest kolizja. W Twoim przypadku hashmap będzie stosunkowo statyczny. Znasz nazwy funkcji, które wspierasz. Radzę więc stworzyć prosty przypadek testowy, w którym wywołujesz unordered_map<...> :: hash_function ze wszystkimi kluczami, aby upewnić się, że nic nie koliduje. Potem możesz o tym zapomnieć.

Szybkie google dla potencjalnych ulepszeń funkcji hashowych mnie tam zaprowadziło:

A fiew good hash functions

Może, w zależności od Twojego konwencje nazewnictwa, można poprawić w niektórych aspektach funkcji.

 7
Author: AndreasT,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-26 08:21:41

Cóż, możesz użyć any_map do przechowywania funkcji z różnymi podpisami (ale wywołanie jej będzie niechlujne) i możesz użyć int_map do wywoływania funkcji z określonym podpisem (wygląda ładniej).

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
Author: Jacob,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-26 15:01:20

Próbowałem użyć drugiej odpowiedzi z c++11. Musiałem zmienić ostatnią linijkę. from:
(*iter) ();
do:
(*ITER - >second) ();

Więc kod jest teraz:

    #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
Author: EckhardN,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-21 18:24:16