【问题标题】:How to call a function by its name (std::string) in C++?如何在 C++ 中通过函数名 (std::string) 调用函数?
【发布时间】:2013-10-28 16:38:24
【问题描述】:

我想知道是否有一种从字符串调用函数的简单方法。我知道一个简单的方法,使用 'if' 和 'else'。

int function_1(int i, int j) {
    return i*j;
}

int function_2(int i, int j) {
    return i/j;
}

...
...
...

int function_N(int i, int j) {
    return i+j;
}

int main(int argc, char* argv[]) {
    int i = 4, j = 2;
    string function = "function_2";
    cout << callFunction(i, j, function) << endl;
    return 0;
}

这是基本方法

int callFunction(int i, int j, string function) {
    if(function == "function_1") {
        return function_1(i, j);
    } else if(function == "function_2") {
        return function_2(i, j);
    } else if(...) {

    } ...
    ...
    ...
    ...
    return  function_1(i, j);
}

还有更简单的吗?

/* New Approach */
int callFunction(int i, int j, string function) {
    /* I need something simple */
    return function(i, j);
}

【问题讨论】:

    标签: c++ string function invoke code-cleanup


    【解决方案1】:

    您还可以将函数放入共享库中。您将使用 dlopen() 动态加载此类库,然后只需使用 std::string 调用函数。举个例子:

    你好.cpp

    #include <iostream>
    
    extern "C" void hello() {
        std::cout << "hello" << '\n';
    }
    

    main.cpp

    #include <iostream>
    #include <dlfcn.h>
    
    int main() {
        using std::cout;
        using std::cerr;
    
        cout << "C++ dlopen demo\n\n";
    
        // open the library
        cout << "Opening hello.so...\n";
        void* handle = dlopen("./hello.so", RTLD_LAZY);
    
        if (!handle) {
            cerr << "Cannot open library: " << dlerror() << '\n';
            return 1;
        }
    
        // load the symbol
        cout << "Loading symbol hello...\n";
        typedef void (*hello_t)();
    
        // reset errors
        dlerror();
    
        std::string yourfunc("hello"); // Here is your function
    
        hello_t hello = (hello_t) dlsym(handle, yourfunc.c_str());
        const char *dlsym_error = dlerror();
        if (dlsym_error) {
            cerr << "Cannot load symbol 'hello': " << dlsym_error <<
                '\n';
            dlclose(handle);
            return 1;
        }
    
        // use it to do the calculation
        cout << "Calling hello...\n";
        hello();
    
        // close the library
        cout << "Closing library...\n";
        dlclose(handle);
    }
    

    编译:

    g++ -fPIC -shared hello.cpp -o hello.so
    

    和:

    g++ main.cpp -o main -ldl
    

    运行:

    C++ dlopen demo
    
    Opening hello.so...
    Loading symbol hello...
    Calling hello...
    hello
    Closing library...
    

    示例是从here 窃取的。 There你可以找到关于dlopen()和c++的更详细的解释

    【讨论】:

    • 我怀疑虚幻引擎就是这样做的;它创建了一个游戏的 .dll,其中可能包含它可以引用的特定于游戏的代码。例如,在输入处理中,您可以按名称将函数作为字符串绑定到输入操作。
    【解决方案2】:

    使用标准字符串到标准函数的映射。

    #include <functional>
    #include <map>
    #include <string>
    #include <iostream>
    
    int add(int x, int y) {return x+y;}
    int sub(int x, int y) {return x-y;}
    
    int main()
    {
        std::map<std::string, std::function<int(int,int)>>  funcMap =
             {{ "add", add},
              { "sub", sub}
             };
    
        std::cout << funcMap["add"](2,3) << "\n";
        std::cout << funcMap["sub"](5,2) << "\n";
    }
    

    使用 Lambda 会更好:

    #include <functional>
    #include <map>
    #include <string>
    #include <iostream>
    
    int main()
    {
        std::map<std::string, std::function<int(int,int)>>  funcMap =
             {{ "add", [](int x, int y){return x+y;}},
              { "sub", [](int x, int y){return x-y;}}
             };
    
        std::cout << funcMap["add"](2,3) << "\n";
        std::cout << funcMap["sub"](5,2) << "\n";
    }
    

    【讨论】:

    • @AlanValejo:当然是这样 :) 在 C++11 中,许多事情变得更加清晰
    【解决方案3】:

    还有另一种可能还没有提到,那就是真正的反射。

    一个选项是访问从可执行文件或共享库中导出的函数,使用操作系统函数将名称解析为地址。这有一些有趣的用途,例如将两个“参赛者”dll 加载到“裁判”程序中,这样人们就可以通过让他们的实际代码相互竞争(玩黑白棋或雷神之锤)来解决它。

    另一个选项是访问编译器创建的调试信息。在 Windows 下,这对于兼容的编译器来说非常容易,因为所有工作都可以卸载到系统 dll 或可从 Microsoft 下载的免费 dll。部分功能已包含在 Windows API 中。

    然而,这更多地属于系统编程的范畴——不管是什么语言——因此它只适用于 C++,因为它是最优秀的系统编程语言。

    【讨论】:

    • 这似乎几乎是我上面答案的缺失部分,这正是我的答案所做的,但方式不同:“使用操作系统函数将名称解析为地址”,除了我的简单是直接并需要您自己的“算法”将字符串解码为地址。
    • reflection 思想的要点是访问编译器创建的元数据,而不必定义带有函数名称和指针的表。除了导出的名称和调试符号之外,还有 RTTI(带有一些编译器)以及混合方法,例如处理编译器创建的 .map 文件以在函数名称和地址之间进行映射。 JEDI 项目为堆栈遍历执行此操作,但它也可用于查找名称的地址。这个想法是使用编译器生成的反映源代码的数据,而不必定义自己的表。
    【解决方案4】:

    您所描述的称为反射,C++ 不支持它。但是,您可能会提供一些解决方法,例如在这个非常具体的情况下,您可能会使用 std::map 将函数名称(std::string 对象)映射到函数指针,如果函数具有相同的原型可能比看起来更容易:

    #include <iostream>
    #include <map>
    
    int add(int i, int j) { return i+j; }
    int sub(int i, int j) { return i-j; }
    
    typedef int (*FnPtr)(int, int);
    
    int main() {
        // initialization:
        std::map<std::string, FnPtr> myMap;
        myMap["add"] = add;
        myMap["sub"] = sub;
    
        // usage:
        std::string s("add");
        int res = myMap[s](2,3);
        std::cout << res;
    }
    

    注意myMap[s](2,3) 检索映射到字符串s 的函数指针并调用该函数,将23 传递给它,使该示例的输出为5

    【讨论】:

    • 您可以使用 std::function 和初始化器列表语法稍微改进。
    • @LokiAstari: std::function 我猜是 C++11。
    • @LihO:我喜欢你的方法。谢谢。
    • @AlanValejo:您可能没有可用的 C++11 支持。试试#include &lt;functional&gt;,如果不行,试试#include &lt;tr1/functional&gt;
    • @AlanValejo:tr1 代表技术报告,它可能允许您使用一些 C++11 功能,即使 C++11 尚不支持。然而,TR1 可能都不可用。
    猜你喜欢
    • 2014-04-12
    • 1970-01-01
    • 2020-06-18
    • 2020-02-17
    • 1970-01-01
    • 2019-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多