2013-04-02 34 views
5

Tôi đang cố gắng tạo danh sách đối số cho một cuộc gọi hàm trong thời gian chạy, nhưng tôi không thể nghĩ ra cách để thực hiện điều này trong C++.Tự động tạo danh sách đối số hàm C++ khi chạy

Đây là thư viện trợ giúp tôi viết. Tôi đang lấy dữ liệu đầu vào từ máy khách qua mạng và sử dụng dữ liệu đó để thực hiện cuộc gọi đến một con trỏ hàm mà người dùng đã đặt trước đó. Hàm này nhận một chuỗi (các mã thông báo, giống như printf) và một số lượng các đối số khác nhau. Những gì tôi cần là một cách để thêm nhiều đối số tùy thuộc vào dữ liệu nào đã được nhận từ máy khách.

Tôi đang lưu trữ các chức năng trong bản đồ về con trỏ hàm

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

Một cách sử dụng ví dụ sẽ là

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" 

này hoạt động độc đáo khi thể xác định rõ chức năng gọi và đó là lý lẽ, nhưng nếu tôi đã nhận được dữ liệu "CreateX 10 20", chương trình cần có khả năng làm cho đối số gọi chính nó. ví dụ:

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

Tôi không thể dự đoán cách người dùng sắp xếp các chức năng và mã này trước.

Nếu có ai có đề xuất hoàn thành tác vụ này theo cách khác, vui lòng đề xuất. Tôi cần người dùng có thể "liên kết" một hàm với thư viện và để thư viện gọi nó sau này sau khi nó nhận được dữ liệu từ một khách hàng được nối mạng, một sự gọi lại về bản chất.

+1

Run-time số đối số khác nhau? Không thể có trong C++ AFAIK, hoặc dù sao cũng sẽ rất tệ. Vấn đề (cú pháp) không phải là để nhận chúng, mà là để vượt qua chúng. Mặc dù vậy, nó có thể có trong assembly. Trong C++, bạn muốn sử dụng cấu trúc dữ liệu để lưu trữ các đối số và chuyển cấu trúc này, giống như một 'std :: list '. Tôi muốn đề nghị hãy xem boost.spirit – dyp

+0

Cách này để vượt qua số biến của các đối số không được chấp nhận trong C++ –

+0

Đó là suy nghĩ ban đầu của tôi, nhưng nếu một hàm có 2 loại đối số khác nhau thì tôi không thể lưu trữ chúng lại với nhau . – PudgePacket

Trả lời

6

Đây là một giải pháp C++ 11. Nó không không hỗ trợ các chức năng varargs như printall hoặc printf, điều này là không thể với kỹ thuật này và IMO không thể nào cả, hoặc ít nhất là cực kỳ phức tạp. Tuy nhiên, chức năng này rất khó sử dụng một cách an toàn trong môi trường như của bạn, vì bất kỳ yêu cầu xấu nào từ bất kỳ ứng dụng khách nào đều có thể làm hỏng máy chủ, hoàn toàn không có bất kỳ sự truy đòi nào. Có thể bạn nên chuyển sang giao diện dựa trên vùng chứa để đảm bảo an toàn và ổn định hơn.

Mặt khác, phương pháp này hỗ trợ tất cả (?) Các hàm khác thống nhất.

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

Về cơ bản bạn gọi adaptfunc(your_function), nơi your_function là một chức năng của bất kỳ loại (trừ varargs). Đổi lại, bạn nhận được một đối tượng std::function chấp nhận một vector là boost::any và trả lại một boost::any. Bạn đặt đối tượng này vào số func_map hoặc làm bất cứ điều gì bạn muốn với chúng.

Các loại đối số và số của chúng được kiểm tra tại thời điểm thực tế cuộc gọi.

Chức năng trả lại void không được hỗ trợ ngoài hộp vì không hỗ trợ boost::any<void>. Điều này có thể được xử lý dễ dàng bằng cách gói kiểu trả về trong một mẫu đơn giản và chuyên cho void. Tôi đã để nó ra cho rõ ràng.

Dưới đây là một người lái xe thử nghiệm:

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

Như đã đề cập bởi @TonyTheLion, bạn có thể sử dụng boost::variant hoặc boost::any để chọn giữa các loại tại thời gian chạy:

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; 

Các bạn có thể ví dụ sử dụng một khách truy cập tĩnh để phân biệt giữa các loại. Dưới đây là một ví dụ đầy đủ, lưu ý rằng tham số tokens thực sự là không còn cần thiết vì boost::variant biết trong thời gian chạy loại được lưu giữ trong nó:

#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

Bất kỳ lý do nào để sử dụng 'vectơ' thay vì' danh sách' ở đây? – dyp

+1

@DyP Không có lý do cụ thể, 'danh sách' sẽ hoạt động tốt. Tuy nhiên, 'vector' đôi khi được coi là" mặc định "container tuần tự (mặc dù những người khác nói rằng thay vì phải là' deque'), và thường dẫn đến hiệu suất tốt hơn do bố trí bộ nhớ tuần tự của nó. – Philipp

Các vấn đề liên quan