2013-04-02 2 views
5

Я пытаюсь создать список аргументов для вызова функции во время выполнения, но я не могу придумать, как это сделать в C++.Динамическое создание списка аргументов функции C++ во время выполнения

Это для вспомогательной библиотеки, которую я пишу. Я беру входные данные от клиента по сети и используя эти данные для вызова указателя функции, который пользователь установил ранее. Функция принимает строку (токенов, похожих на printf) и различное количество аргументов. Мне нужен способ добавления дополнительных аргументов в зависимости от того, какие данные были получены от клиента.

Я храню функции в карте указателей на функции

typedef void (*varying_args_fp)(string,...); 
map<string,varying_args_fp> func_map; 

Пример использования будет

void printall(string tokens, ...) 
{ 
    va_list a_list; 
    va_start(a_list, tokens); 

    for each(auto x in tokens) 
    { 
     if (x == 'i') 
     { 
      cout << "Int: " << va_arg(a_list, int) << ' '; 
     } 
     else if(x == 'c') 
     { 
      cout << "Char: " << va_arg(a_list, char) << ' '; 
     } 
    } 

    va_end(a_list); 
} 

func_map["printall"] = printall; 
func_map["printall"]("iic",5,10,'x'); 
// prints "Int: 5 Int: 10 Char: x" 

Это хорошо работает, когда жестко прописывать вызов функции, и это аргументы, но если я «Получив данные« CreateX 10 20 », программа должна быть способна сама вызвать аргумент. например

// func_name = "CreateX", tokens = 'ii', first_arg = 10, second_arg = 20 
func_map[func_name](tokens,first_arg,second_arg); 

Я не могу предсказать, как пользователи будут заранее излагать функции и кодировать их заранее.

Если у кого есть предложения по выполнению этой задачи другим способом, не стесняйтесь предлагать. Мне нужно, чтобы пользователь мог «привязать» функцию к библиотеке, а для того, чтобы библиотека вызывала ее позже после того, как она получила данные от сетевого клиента, по сути, обратный вызов.

+1

время выполнения варьируя количество аргументов? Невозможно в C++ AFAIK, иначе было бы очень плохо. Синтаксическая проблема заключается не в их получении, а в переносе. Однако это должно быть возможно в ассемблере. В C++ вы предпочитаете использовать структуру данных для хранения аргументов и передавать эту структуру, например, 'std :: list '. Я бы предложил взглянуть на boost.spirit – dyp

+0

Этот способ передачи переменной количества аргументов устарел в C++ –

+0

Это была моя первоначальная мысль, но если функция принимает два разных типа аргументов, то я не могу их хранить вместе , – PudgePacket

ответ

6

Вот раствор C++ 11. Он не поддерживает функции varargs, такие как printall или printf, это невозможно с помощью этой техники, и IMO невозможно вообще или, по крайней мере, чрезвычайно сложно. Такая функция трудно использовать безопасно в такой среде, как ваша, так как любой плохой запрос от любого клиента может привести к краху сервера, при этом абсолютно никакого обращения. Вероятно, вам необходимо перейти на контейнерный интерфейс для лучшей безопасности и стабильности.

С другой стороны, этот метод поддерживает все (?) Другие функции равномерно.

#include <vector> 
#include <iostream> 
#include <functional> 
#include <stdexcept> 
#include <string> 
#include <boost/any.hpp> 


template <typename Ret, typename... Args> 
Ret callfunc (std::function<Ret(Args...)> func, std::vector<boost::any> anyargs); 

template <typename Ret> 
Ret callfunc (std::function<Ret()> func, std::vector<boost::any> anyargs) 
{ 
    if (anyargs.size() > 0) 
     throw std::runtime_error("oops, argument list too long"); 
    return func(); 
} 

template <typename Ret, typename Arg0, typename... Args> 
Ret callfunc (std::function<Ret(Arg0, Args...)> func, std::vector<boost::any> anyargs) 
{ 
    if (anyargs.size() == 0) 
     throw std::runtime_error("oops, argument list too short"); 
    Arg0 arg0 = boost::any_cast<Arg0>(anyargs[0]); 
    anyargs.erase(anyargs.begin()); 
    std::function<Ret(Args... args)> lambda = 
     ([=](Args... args) -> Ret { 
     return func(arg0, args...); 
    }); 
    return callfunc (lambda, anyargs); 
} 

template <typename Ret, typename... Args> 
std::function<boost::any(std::vector<boost::any>)> adaptfunc (Ret (*func)(Args...)) { 
    std::function<Ret(Args...)> stdfunc = func; 
    std::function<boost::any(std::vector<boost::any>)> result = 
     ([=](std::vector<boost::any> anyargs) -> boost::any { 
     return boost::any(callfunc(stdfunc, anyargs)); 
     }); 
    return result; 
} 

В основном вы называете adaptfunc(your_function), где your_function является функцией любого типа (за исключением списков параметров). Взамен вы получаете объект std::function, который принимает вектор boost::any и возвращает boost::any. Вы помещаете этот объект в свой func_map или делаете все, что хотите с ними.

Типы аргументов и их число проверяются во время фактического вызова.

Функции, возвращаемые void, не поддерживаются из коробки, потому что boost::any<void> не поддерживается. С этим можно легко справиться, обернув тип возврата в простой шаблон и специализируясь на void. Я оставил это для ясности.

Вот тест драйвер:

int func1 (int a) 
{ 
    std::cout << "func1(" << a << ") = "; 
    return 33; 
} 

int func2 (double a, std::string b) 
{ 
    std::cout << "func2(" << a << ",\"" << b << "\") = "; 
    return 7; 
} 

int func3 (std::string a, double b) 
{ 
    std::cout << "func3(" << a << ",\"" << b << "\") = "; 
    return 7; 
} 

int func4 (int a, int b) 
{ 
    std::cout << "func4(" << a << "," << b << ") = "; 
    return a+b; 
} 


int main() 
{ 
    std::vector<std::function<boost::any(std::vector<boost::any>)>> fcs = { 
     adaptfunc(func1), adaptfunc(func2), adaptfunc(func3), adaptfunc(func4) }; 

    std::vector<std::vector<boost::any>> args = 
    {{777}, {66.6, std::string("yeah right")}, {std::string("whatever"), 0.123}, {3, 2}}; 

    // correct calls will succeed 
    for (int i = 0; i < fcs.size(); ++i) 
     std::cout << boost::any_cast<int>(fcs[i](args[i])) << std::endl; 

    // incorrect calls will throw 
    for (int i = 0; i < fcs.size(); ++i) 
     try { 
      std::cout << boost::any_cast<int>(fcs[i](args[fcs.size()-1-i])) << std::endl; 
     } catch (std::exception& e) { 
      std::cout << "Could not call, got exception: " << e.what() << std::endl; 
     } 
} 
2

Как уже упомянуто @TonyTheLion, вы можете использовать boost::variant или boost::any для выбора между типами во время выполнения:

typedef std::function<void(const std::string&, const std::vector<boost::variant<char, int>>&)> varying_args_fn; 
std::map<std::string, varying_args_fn> func_map; 

вы можете, например, используйте статического посетителя, чтобы различать типы. Вот полный пример, обратите внимание, что параметр tokens не является на самом деле больше нет необходимости, так как boost::variant знает во время выполнения какого типа хранятся в нем:

#include <map> 
#include <vector> 
#include <string> 
#include <functional> 
#include <iostream> 

#include <boost/variant.hpp> 
#include <boost/any.hpp> 

typedef std::function<void(const std::string&, const std::vector<boost::variant<char, int>>&)> varying_args_fn; 

void printall(const std::string& tokens, const std::vector<boost::variant<char, int>>& args) { 
    for (const auto& x : args) { 
    struct : boost::static_visitor<> { 
     void operator()(int i) { 
     std::cout << "Int: " << i << ' '; 
     } 
     void operator()(char c) { 
     std::cout << "Char: " << c << ' '; 
     } 
    } visitor; 
    boost::apply_visitor(visitor, x); 
    } 
} 

int main() { 
    std::map<std::string, varying_args_fn> func_map; 
    func_map["printall"] = printall; 
    func_map["printall"]("iic", {5, 10, 'x'}); 
} 
+0

Любая причина использовать 'vector' вместо' list' здесь? – dyp

+1

@DyP Никакая конкретная причина, 'list' не будет работать. Однако «вектор» иногда считается «стандартным» последовательным контейнером (хотя другие говорят, что это скорее «deque»), и часто приводит к лучшей производительности из-за его последовательного расположения памяти. – Philipp

Смежные вопросы