web-dev-qa-db-ja.com

関数ポインターのSTLマップを使用する

多くの組み込み関数を備えたスクリプトエンジンを開発したため、任意の関数を呼び出すために、コードはif .. else if .. else if名前を確認する壁ですが、より効率的なソリューションを開発したいと思います。

hashmapをキーとしての文字列と値としてのポインターを使用する必要がありますか? STLマップを使用してどのように行うことができますか?

[〜#〜] edit [〜#〜]:私の頭に浮かんだもう1つのポイント:もちろん、マップを使用すると、コンパイラーは関数をインライン化しないように強制されますが、私の非効率的なアプローチには何もありませんでした関数呼び出しの必要性によって発生するオーバーヘッドは、コードを実行するだけです。

だから、関数呼び出しによって生成されるオーバーヘッドは、if..else chain ..それ以外の場合、実行時に文字をチェックすることで比較の数を最小限に抑えることができます(長くなりますが、高速になります)。

36
Jack

関数のシグネチャは何でも:

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*/>に一般化できるため、関数ポインターだけでなく、呼び出し可能なものをすべてサポートできることに注意してください。

43
GManNickG

Boost.Function および Boost.Bind を使用して、heterogeneous関数:

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

もちろん、互換性のあるプロトタイプの機能のマップにすることもできます。

16
mloskot

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

上記の回答は完全な概要を示しているようですが、これはあなたの2番目の質問のみに関係します:

キーによるマップ要素の取得にはO(log n)の複雑さがあります。キーによるハッシュマップの取得には、O(1)複雑さ+衝突の場合の側面に少しのものがあります。関数名に適切なハッシュ関数がある場合は、それを使用します。それは問題ないはずです。

ただし、100要素未満の要素は何も役に立たないことに注意してください。

ハッシュマップの唯一の欠点は衝突です。あなたの場合、ハッシュマップは比較的静的です。サポートする関数名を知っています。したがって、単純なテストケースを作成し、すべてのキーでunordered_map <...> :: hash_functionを呼び出して、何も衝突しないことを確認することをお勧めします。その後、あなたはそれを忘れることができます。

ハッシュ関数の潜在的な改善のための簡単なグーグルが私にそこに着きました:

少数の適切なハッシュ関数

命名規則によっては、機能のいくつかの側面を改善できる場合があります。

7
AndreasT

まあ、_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;
}
_
3
Jacob

私はc ++ 11で2番目の答えを使用しようとしました。最後の行を次から変更する必要がありました。
(* iter)();
に:
(* iter-> second)();

したがって、コードは次のようになります。

    #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

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
Ia Rigby