2013-04-02 3 views
5

Sto provando a generare un elenco di argomenti per una chiamata di funzione durante il runtime, ma non riesco a pensare a un modo per farlo in C++.Creazione dinamica di un elenco di argomenti della funzione C++ in fase di esecuzione

Questo è per una libreria di supporto che sto scrivendo. Sto prendendo i dati di input dal client su una rete e usando quei dati per effettuare una chiamata a un puntatore a funzione che l'utente ha impostato in precedenza. La funzione accetta una stringa (di token, simile a printf) e una quantità variabile di argomenti. Quello di cui ho bisogno è un modo per aggiungere più argomenti a seconda di quali dati sono stati ricevuti dal client.

sto memorizzare le funzioni in una mappa di puntatori a funzione

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

Un esempio d'uso potrebbe essere

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" 

Questo funziona bene quando hardcoding la chiamata di funzione ed è argomenti, ma se io Abbiamo ricevuto i dati "CreateX 10 20", il programma deve essere in grado di fare in modo che l'argomento si chiami da solo. es.

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

Non riesco a prevedere come gli utenti stenderanno le funzioni e codificheranno questo in anticipo.

Se qualcuno ha suggerimenti per eseguire questa operazione in un altro modo, sentiti libero di suggerire. Ho bisogno che l'utente sia in grado di "associare" una funzione alla libreria e che la libreria la chiami più tardi dopo aver ricevuto i dati da un client di rete, un callback in sostanza.

+1

runtime numero di argomenti variabile? Non è possibile in C++ AFAIK, o sarebbe comunque molto brutto. Il problema (sintattico) non è quello di riceverli, ma piuttosto di passarli. Dovrebbe essere possibile in assembler, però. In C++, preferisci usare una struttura dati per archiviare gli argomenti e passare questa struttura, come un 'std :: list '. Suggerisco di dare un'occhiata a boost.spirit – dyp

+0

Questo modo di passare il numero variabile di argomenti è deprecato in C++ –

+0

Questo era il mio pensiero iniziale, ma se una funzione richiede 2 diversi tipi di argomenti, quindi non posso memorizzarli insieme . – PudgePacket

risposta

6

qui è una soluzione C++ 11. Lo fa non funzioni varargs di supporto come printall o printf, questo è impossibile con questa tecnica e IMO impossibile a tutti, o per lo meno estremamente difficile. Tale funzione è difficile da utilizzare in sicurezza in un ambiente come il tuo in ogni caso, dal momento che qualsiasi richiesta errata da parte di qualsiasi client potrebbe causare l'arresto anomalo del server, senza alcun ricorso. Probabilmente dovresti passare all'interfaccia basata su container per una maggiore sicurezza e stabilità.

D'altra parte, questo metodo supporta tutte le (?) Altre funzioni in modo uniforme.

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

Fondamentalmente si chiamano adaptfunc(your_function), dove your_function è una funzione di qualsiasi tipo (ad eccezione varargs). In cambio si ottiene un oggetto std::function che accetta un vettore di boost::any e restituisce un boost::any. Metti questo oggetto nel tuo func_map, o fai qualsiasi altra cosa desideri con loro.

I tipi di argomenti e il loro numero vengono controllati al momento della chiamata effettiva.

Le funzioni di restituzione void non sono supportate immediatamente, perché boost::any<void> non è supportato. Questo può essere risolto facilmente avvolgendo il tipo restituito in un modello semplice e specializzandosi per void. L'ho lasciato fuori per chiarezza.

Ecco un collaudatore:

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

Come già detto da @TonyTheLion, è possibile utilizzare boost::variant o boost::any per scegliere tra i tipi in fase di esecuzione:

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; 

La si può ad esempio utilizzare un visitatore statico per distinguere tra i tipi. Ecco un esempio completo, notare che il parametro tokens è in realtà non è più necessario in quanto boost::variant sa a runtime tipo memorizzato in esso:

#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

Qualche motivo per usare un 'vector' invece di un' elenco' qui? – dyp

+1

@DyP Nessun motivo specifico, 'lista' funzionerebbe pure. Tuttavia, 'vector' è talvolta considerato il contenitore sequenziale" predefinito "(sebbene altri affermino che dovrebbe piuttosto essere" deque'), e spesso porta a prestazioni migliori a causa del suo layout di memoria sequenziale. – Philipp