Подтвердить что ты не робот

Использование STL-карты указателей функций

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

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

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

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

4b9b3361

Ответ 1

Независимо от ваших сигнатур функций:

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*/>, чтобы вы могли поддерживать любую вызываемую вещь, а не только точно указатели функций.

Ответ 2

Вы также можете использовать 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));

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

Ответ 3

В С++ 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;
}

Ответ 4

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

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

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

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

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

Хорошие хеш-функции fiew

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

Ответ 5

Ну, вы можете использовать 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;
}

Ответ 6

Я попытался использовать второй ответ с С++ 11. Мне пришлось изменить последнюю строку от:
 (* ИТЭР)();
в:
(* 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;
    }

Ответ 7

Мне удалось изменить пример из 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;
}