Использование STL-карты указателей на функции - PullRequest
35 голосов
/ 26 января 2010

Я разработал механизм сценариев, который имеет много встроенных функций, поэтому для вызова любой функции мой код просто вошел в if .. else if .. else if стену, проверяющую имя, но я хотел бы разработать более эффективное решение.

Должен ли я использовать hashmap со строками в качестве ключей и указателями в качестве значений? Как я могу сделать это, используя карту STL?

EDIT : Еще один момент, который пришел мне в голову: конечно, использование карты заставит компилятор не включать встроенные функции, но мой неэффективный подход не имел никаких накладных расходов, вызванных необходимостью вызова функций, он просто выполняет код.

Поэтому мне интересно, будут ли издержки, генерируемые вызовом функции, лучше, чем цепочка if..else. В противном случае я мог бы минимизировать количество сравнений, проверяя символ во время выполнения (будет длиннее, но быстрее).

Ответы [ 7 ]

41 голосов
/ 26 января 2010

Какими бы ни были ваши сигнатуры функций:

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

Обратите внимание, что тип ScriptFunction можно обобщить до std::function</* whatever*/>, так что вы можете поддерживать любую вызываемую вещь, а не только точные указатели функций.

16 голосов
/ 26 января 2010

Вы также можете использовать Boost.Function и Boost.Bind , что даже позволяет в некоторой степени иметь карту гетерогенных функций:

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

Конечно, это может быть и карта функций совместимых прототипов.

13 голосов
/ 21 ноября 2015

В C ++ 11 вы можете сделать что-то вроде этого: Этому интерфейсу нужен только тип возврата, и он заботится обо всем остальном со стороны вызывающей стороны.

#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;
}
7 голосов
/ 26 января 2010

Вышеприведенные ответы дают полный обзор, это касается только вашего второго вопроса:

Поиск элемента карты по ключу имеет сложность O (log n). Поиск по хэш-карте по ключу имеет сложность O (1) + мелочи на стороне в случае коллизий. Так что, если есть хорошая хеш-функция для имен ваших функций, используйте ее. Ваша реализация будет стандартной. Это должно быть хорошо.

Но имейте в виду, что все, что меньше ста элементов, не принесет слишком много пользы.

Единственным недостатком хэш-карты является столкновение. В вашем случае, hashmap будет относительно статичным. Вы знаете имена функций, которые вы поддерживаете. Поэтому я советую вам создать простой тестовый пример, в котором вы вызываете unordered_map <...> :: hash_function со всеми вашими ключами, чтобы убедиться, что ничего не конфликтует. После этого вы можете забыть об этом.

Быстрый гугл для потенциальных улучшений хеш-функций получил меня там:

Несколько хороших хеш-функций

Возможно, в зависимости от ваших соглашений об именах, вы можете улучшить некоторые аспекты этой функции.

3 голосов
/ 26 января 2010

Ну, вы можете использовать any_map для хранения функций с разными подписями (но вызов будет беспорядочным), и вы можете использовать int_map для вызова функций с определенной подписью (выглядит лучше).

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;
}
0 голосов
/ 05 февраля 2019

Мне удалось изменить пример из Mohit для работы с указателями на функции-члены:

#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 голосов
/ 21 апреля 2014

Я пытался использовать второй ответ с C ++ 11. Я должен был изменить последнюю строку от:
(* ITER) ();
чтобы:
(* Iter-> второй) ();

поэтому код теперь:

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