Как вызвать функцию по имени (std::string) в C++?

Интересно, есть ли простой способ вызвать функцию из строки? Я знаю простой способ, используя "если" и "еще".

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

5 ответов

Решение

То, что вы описали, называется отражением, и 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 и вызывает эту функцию, передавая 2 а также 3 чтобы сделать вывод этого примера 5

Использование карты стандартной строки для стандартных функций.

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

Еще лучше с лямбдой:

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

Вы также можете поместить свои функции в общую библиотеку. Вы будете загружать такую ​​библиотеку динамически с помощью dlopen(), а затем просто вызывать функции с помощью std::string. Вот пример:

hello.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...

Пример был украден отсюда. Там вы можете найти более подробное объяснение по dlopen() и C++

Есть еще одна возможность, которая еще не была упомянута, которая является истинным отражением.

Вариант для этого - доступ к функциям, экспортированным из исполняемого файла или общей библиотеки, с использованием функций операционной системы для преобразования имен в адреса. У этого есть интересные применения, такие как загрузка двух "конкурирующих" библиотек в программу "судья", так что люди могут убить их, если их действительные коды будут сражаться друг с другом (играя в реверси или Quake, что угодно).

Другой вариант - доступ к отладочной информации, созданной компилятором. Под Windows это может быть удивительно легко для совместимых компиляторов, поскольку вся работа может быть выгружена в системные библиотеки или бесплатные библиотеки, загружаемые из Microsoft. Часть функциональности уже содержится в Windows API.

Тем не менее, это больше относится к категории системного программирования - независимо от языка - и, следовательно, оно относится к C++ только постольку, поскольку это язык системного программирования.

Аналогично ответу Java, но загружает функции из текущей программы:

      #include <iostream>
#include <dlfcn.h>

extern "C"
void test()
{
    std::cout << __PRETTY_FUNCTION__ << "\n";
}

extern "C"
void test2()
{
    std::cout << __func__ << "\n";
}

void something_the_user_is_not_supposed_to_call()
{
    std::cout << "Oops...\n";
}

int main(int argc, char** argv)
{
    if (argc < 2)
    {
        std::cout << "Usage: " << argv[0] << " [function_name]\n";
        return 1;
    }

    void* self = dlopen(nullptr, RTLD_LAZY);

    if (!self)
    {
        std::cerr << dlerror() << "\n";
        return 1;
    }

    void* function = dlsym(self, argv[1]);
    
    if (!function)
    {
        std::cerr << dlerror() << "\n";
        return 1;
    }

    reinterpret_cast<void(*)()>(function)();

    dlclose(self);
}

Затем скомпилируйте с помощью-rdynamicопция, поэтому все символы экспортируются:

      $ g++ test.cpp -o test -ldl -rdynamic

И это работает:

      $ ./test test
void test()
$ ./test test2
test2
$ ./test test3
./test: undefined symbol: test3

Конечно, делать это — ужасная идея:

      $ ./test _Z42something_the_user_is_not_supposed_to_callv
Oops...
$ ./test abort
Aborted (core dumped)
$ ./test sleep
^C
$ ./test main
Segmentation fault (core dumped)
Другие вопросы по тегам