我正在尝试在运行时为函数调用生成一个参数列表,但我想不出在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);
我无法预测用户将如何预先布置功能和编码。
如果有人建议以另一种方式完成此任务,请随时提出建议。我需要用户能够将一个函数“绑定”到库中,并且让库在以后从网络客户端接收数据后调用它,本质上就是回调。
答案 0 :(得分: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
是任何类型的函数(除了varargs)。作为回报,您会得到一个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;
}
}
答案 1 :(得分: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'});
}