2013-04-02 13 views
5

Próbuję wygenerować listę argumentów dla wywołania funkcji w czasie wykonywania, ale nie mogę wymyślić sposobu, aby osiągnąć to w języku C++.Dynamiczne tworzenie listy argumentów funkcji C++ w czasie wykonywania

To jest dla biblioteki pomocniczej, którą piszę. Biorę dane wejściowe od klienta przez sieć i korzystam z tych danych, aby wykonać wywołanie wskaźnika funkcji, który użytkownik ustawił wcześniej. Funkcja pobiera ciąg znaków (z tokenów, podobny do printf) i różną liczbę argumentów. Potrzebuję sposobu na dodanie większej liczby argumentów w zależności od tego, jakie dane zostały odebrane od klienta.

mam przechowywania funkcje mapy wskaźników funkcji

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

Przykładem użycia może być

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" 

Działa to dobrze, gdy hardcoding wywołanie funkcji i to argumenty, ale jeśli "Otrzymałem dane" CreateX 10 20 ", program musi mieć możliwość wywołania samego argumentu. np.

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

Nie mogę przewidzieć, jak użytkownicy będą układać funkcje i kodować to wcześniej.

Jeśli ktoś ma sugestie dotyczące wykonania tego zadania w inny sposób, zachęcamy do zasugerowania. Potrzebuję użytkownika, aby móc "związać" funkcję z biblioteką i aby biblioteka mogła wywołać ją później, po otrzymaniu danych od klienta sieciowego, w skrócie oddzwanianie.

+1

Run-time zmiennej liczby argumentów? Nie jest to możliwe w C++ AFAIK, albo i tak byłoby bardzo źle. Problem (syntaktyczny) nie polega na przyjmowaniu ich, lecz na ich przekazywaniu. Jednak powinno to być możliwe w asemblerze. W C++, wolisz używać struktury danych do przechowywania argumentów i przekazywania tej struktury, jak 'std :: list '. Proponuję rzucić okiem na boost.spirit – dyp

+0

Ten sposób przekazywania zmiennej liczby argumentów jest przestarzały w C++ –

+0

To była moja pierwsza myśl, ale jeśli funkcja przyjmuje 2 różne typy argumentów, to nie mogę ich przechowywać razem . – PudgePacket

Odpowiedz

6

Oto C++ 11 rozwiązaniem. Nie obsługuje ona takich funkcji, jak lub printf, jest to niemożliwe przy tej technice i IMO w ogóle niemożliwe lub przynajmniej niezwykle trudne. Taka funkcja jest trudna do bezpiecznego użytkowania w takim środowisku jak twoje, ponieważ każda zła prośba dowolnego klienta może spowodować awarię serwera, bez żadnego regresu. Prawdopodobnie powinieneś przejść do interfejsu opartego na kontenerze, aby uzyskać większe bezpieczeństwo i stabilność.

Z drugiej strony ta metoda obsługuje wszystkie (?) Inne funkcje jednolicie.

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

Zasadniczo zadzwonić adaptfunc(your_function), gdzie your_function jest funkcją dowolnego typu (z wyjątkiem varargs). W zamian otrzymujesz obiekt std::function, który akceptuje wektor boost::any i zwraca boost::any. Umieszczasz ten obiekt w swoim func_map lub robisz, co tylko chcesz z nimi.

Rodzaje argumentów i ich liczba są sprawdzane podczas faktycznego połączenia.

Funkcje zwracające void nie są obsługiwane po wyjęciu z pudełka, ponieważ boost::any<void> nie jest obsługiwany. Można to łatwo rozwiązać, zawijając typ zwrotu w prosty szablon i specjalizując się w void. Zostawiłem to dla jasności.

Oto kierowca testowy:

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

Jak już wspomniano przez @TonyTheLion, można użyć boost::variant lub boost::any wybrać między typami w czasie wykonywania:

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; 

się poniżej przykład użyj statycznego gościa, aby rozróżnić typy. Oto pełna przykładów, trzeba pamiętać, że parametr tokens jest rzeczywiście nie jest już konieczne, ponieważ w czasie wykonywania boost::variant wie, jakiego typu jest przechowywany w nim:

#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

Jakiś powód, aby użyć tutaj 'wektora' zamiast' listy'? – dyp

+1

@DyP Bez konkretnego powodu, 'list' również by działał. Jednak "wektor" jest czasem uważany za "domyślny" sekwencyjny kontener (chociaż inni mówią, że powinien to być raczej "deque") i często prowadzi do lepszej wydajności ze względu na sekwencyjny układ pamięci. – Philipp

Powiązane problemy