Как вызвать функцию по ее имени (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);
}

person Alan Valejo    schedule 20.10.2013    source источник


Ответы (4)


То, что вы описали, называется reflection и 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

person LihO    schedule 20.10.2013
comment
Вы можете немного улучшить это, используя std::function и синтаксис списков инициализатора. - person Martin York; 20.10.2013
comment
@LokiAstari: std::function это С++ 11, я думаю. - person LihO; 20.10.2013
comment
@LihO: мне нравится твой подход. Спасибо. - person Alan Valejo; 20.10.2013
comment
@LokiAstari: я проверю std::function - person Alan Valejo; 20.10.2013
comment
@AlanValejo: Возможно, у вас нет поддержки C++11. Попробуйте #include <functional>, а если не получится, попробуйте #include <tr1/functional> - person LihO; 20.10.2013
comment
Спасибо, попробую. Но я никогда не видел "tr1", мне нужно проверить. «tr1» работает в Linux? - person Alan Valejo; 20.10.2013
comment
@AlanValejo: tr1 означает технический отчет, который может позволить вам использовать некоторые функции C++11, даже если C++11 еще не поддерживается. Однако возможно, что ни один из TR1 не будет доступен. - person LihO; 20.10.2013
comment
Если функция принадлежит классу, будет: map[name] = class-›func? - person Alan Valejo; 20.10.2013
comment
@AlanValejo: Это было бы больше похоже на obj->(*map["name"])() - person LihO; 20.10.2013
comment
См. этот пост: заголовок stackoverflow.com/questions/19480281/ - person Alan Valejo; 20.10.2013

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

#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";
}
person Martin York    schedule 20.10.2013
comment
@AlanValejo: Конечно :) Многие вещи стали намного чище в C++11. - person LihO; 20.10.2013

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

Пример был украден из здесь. Здесь вы можете найти более подробное объяснение dlopen() и c++

person jav    schedule 13.02.2019
comment
Я подозреваю, что именно так работает Unreal Engine; это, поскольку он создает .dll игры, которая, вероятно, содержит специфичный для игры код, на который он затем может ссылаться. Например, в обработке ввода, где вы можете привязать функцию по имени в виде строки к действию ввода. - person Casey; 21.06.2021

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

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

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

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

person DarthGizka    schedule 11.11.2014
comment
Это похоже на отсутствующую часть моего ответа выше, и это именно то, что делает мой ответ, но по-другому: использование функций операционной системы для преобразования имен в адреса, кроме моего, просто является прямым и требует вашего собственного «алгоритм» для декодирования строка на адрес. - person AMDG; 30.11.2014
comment
Суть идеи reflection заключается в доступе к метаданным, созданным компилятором, вместо определения таблиц с именами функций и указателями. Помимо экспортируемых имен и символов отладки, есть также RTTI (с некоторыми компиляторами), а также гибридные подходы, такие как обработка файла .map, созданного компилятором для отображения между именами функций и адресами. Проект JEDI делает это для обхода стека, но его также можно использовать для поиска адресов для имен. Идея состоит в том, чтобы использовать сгенерированные компилятором данные, отражающие исходный код, вместо того, чтобы определять собственные таблицы. - person DarthGizka; 04.12.2014