2013-04-02 25 views
5

我想在運行時生成一個函數調用的參數列表,但我想不出在C++中完成此操作的方法。在運行時動態創建一個C++函數參數列表

這是我正在寫的幫助程序庫。我通過網絡從客戶端獲取輸入數據,並使用該數據調用用戶之前設置的函數指針。該函數需要一個字符串(類似於printf的標記)和不同數量的參數。我需要的是根據從客戶端接收到什麼數據添加更多參數的方法。

我存儲功能的map函數指針的

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的解決方案。它不支持而不支持支持可變參數功能,如printallprintf,這是不可能的,這種技術和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::variantboost::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'也可以。然而,'vector'有時被認爲是默認的順序容器(儘管其他人認爲它應該是'deque'),並且由於其順序的內存佈局,往往會導致更好的性能。 – Philipp