2010-01-26 16 views
31

Opracowałem silnik skryptowy, który ma wiele wbudowanych funkcji, więc aby wywołać dowolną funkcję, mój kod po prostu wszedł w ścianę if .. else if .. else if sprawdzając nazwę, ale chciałbym opracować bardziej wydajne rozwiązanie .Używanie mapy wskaźników funkcji STL

Czy powinienem używać wartości hashmap z ciągami jako kluczami i wskaźnikami jako wartościami? Jak mogłem to zrobić za pomocą mapy STL?

EDIT: Kolejnym punktem, który przyszedł mi do głowy: oczywiście za pomocą mapy zmusi kompilator nie inline funkcji, ale mój nieefektywne podejście nie mają żadnych narzutów generowane 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 łańcucha if..else .. w przeciwnym razie mógłbym zminimalizować liczbę porównań, sprawdzając znak w czasie wykonywania (będzie dłuższy, ale szybszy).

Odpowiedz

36

Niezależnie podpisy funkcyjne są:

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

uwagę, że typ ScriptFunction mogą być uogólnione na std::function</* whatever*/> więc można wspierać wszelkie wpłacone rzecz, nie tylko dokładnie wskaźników funkcji.

+1

Również nie ma potrzeby używania prawdziwej tabeli mieszającej, takiej jak 'unordered_map'. Nie będzie tak wielu elementów, że tabelka mieszająca przyniesie korzyści wydajnościowe, nie zdziwiłbym się nawet, gdyby w tym przypadku "mapa" była szybsza. – sth

+3

Właściwie zrobiłem kilka podobnych rzeczy, a 'unordered_map' było * znacznie * szybsze. Miałem w nim tylko 10 000 rzeczy, a profilowałem zarówno mapy "map" i "unordered_map". – GManNickG

+1

Spodziewałbym się "wielu wbudowanych funkcji" << 10.000'. Hasmap w przypadku OP ma wyraźną przewagę bycia "prawdziwym O (1)", ponieważ nie musi rosnąć, a dla łańcucha można skonstruować hasz bezkolizyjny. Wątpię, aby różnica była * znacząca * w porównaniu z "mapą" nawet na kilka 100 pozycji. – peterchen

3

Można użyć funkcji any_map do zapisywania funkcji o różnych sygnaturach (ale wywołanie tego będzie kłopotliwe) i można użyć funkcji int_map do wywoływania funkcji o określonym sygnaturze (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; 
} 
+1

W rzeczywistości uważam to za bardzo użyteczne. Zasadniczo możesz napisać własne funkcje, które zamykają reinterpretację (np. Float my_b() {return reinterpret .....} –

+3

Czy naprawdę napisałeś 'void main' w programie C++? –

+3

@ LB--: Dlaczego nie edytuj go? Oh czekaj ... 140 rep. – Jacob

15

Można również użyć Boost.Function i Boost.Bind co pozwala nawet, do pewnego stopnia, aby 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ć mapa z funkcji zgodnych prototypów Ależ oczywiście.

+0

To nie działa dla mnie, mam błąd kompilatora. 'boost :: function': zbyt wiele argumentów z szablonu – excray

+0

@ vivek-g, jest wiele problemów , wersja kompilatora, brakujące, itp. Kompiluje się i uruchamia dla mnie, jak również dla kodu pocztowego: http://codepad.org/ciKTrh2r – mloskot

+1

@mloskot, dziękuję za wspaniały przykład! –

7

Powyższe odpowiedzi wydają się dać pełny przegląd, to chodzi tylko swoje drugie pytanie:

Mapa element odbierający kluczem ma O (log n) złożoności. Odzyskiwanie Hashmap przez klucz ma złożoność O (1) + trochę rzeczy na boku w przypadku kolizji. Więc jeśli jest tam dobra funkcja skrótu dla twoich nazw funkcji, użyj jej. Twoja implementacja będzie miała standardową. Powinno być dobrze.

Należy jednak pamiętać, że wszystko poniżej 100 elementów nie przyniesie zbyt wiele korzyści.

Jedyną wadą mapy skrótu jest kolizja. W twoim przypadku hashmap będzie względnie statyczny. Znasz nazwy funkcji, które obsługujesz. Tak więc radzę stworzyć prosty test, w którym wywołasz 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ń dotyczących funkcji skrótu dostał mnie tam:

A fiew good hash functions

Być może, w zależności od konwencji nazewnictwa, można poprawić w niektórych aspektach tej funkcji.

0

Próbowałem użyć drugiej odpowiedzi za pomocą C++ 11. Musiałem zmienić ostatnią linię od:
(* iter)();
do:
(* iter-> sekunda)();

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

w C++ 11 można zrobić coś takiego: Ten interfejs potrzebuje tylko typ zwracany i dba o wszystko inne od strony rozmówcy.

#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

To jest złoto. dodać funkcje członkowskie do miksu? Może w pewnym momencie przesyłając go do typu nie będącego członkiem? – LRP

Powiązane problemy