c++ - 什么是在运行时动态转换和调用映射中的变量参数函数而不使用外部库的好方法
问题描述
假设我有多个带有可变参数的函数:
void greetWorld() {
cout << "Hello World!" << endl;
}
void greetName(const string& name) {
cout << "Hello " << name << "!" << endl;
}
void printAddition(const int lhs, const int rhs) {
cout << "Addition: " << to_string(lhs + rhs) << endl;
}
这些存储在函数映射中std::strings
(函数存储为多态类)。
template<typename... Args>
class DerivedFunction;
class BaseFunction {
public:
template<typename... Args>
void operator()(Args... args) const {
(*static_cast<const DerivedFunction<Args...>*>(this))(args...);
}
};
template<typename... Args>
class DerivedFunction : public BaseFunction {
public:
DerivedFunction(void(*function)(Args...)) {
this->function = function;
}
void operator()(Args... args) const {
function(args...);
}
private:
void(*function)(Args...);
};
template<typename... Args>
unique_ptr<DerivedFunction<Args...>> make_function(
void(*function)(Args...)
) {
return std::make_unique<DerivedFunction<Args...>>(function);
}
int main() {
unordered_map<string, unique_ptr<BaseFunction>> function_map;
function_map.insert({ "greetWorld", make_function(&greetWorld) });
function_map.insert({ "greetName", make_function(&greetName) });
function_map.insert({ "printAddition", make_function(&printAddition) });
...
}
我可以在编译时调用函数,例如:
int main() {
...
(*function_map.at("greetWorld"))();
(*function_map.at("greetName"))("Foo"s);
(*function_map.at("printAddition"))(1, 2);
}
如果我有一个字符串,或者像这样的流:
greetWorld
greetName string Foo
printAddition int 1 int 2
调用函数的好方法是什么?我想不出在运行时转换类型的任何方法。
为什么?
我正在尝试实现某种远程调用程序以用于学习目的。我不想使用外部库,因为我正在尝试学习如何使用 C++ 标准库来实现它,以便更多地了解 C++。
我尝试了什么?
不多。我已经测试过创建以 a std::vector
of std::any
s 作为参数的函数,然后将函数any_cast
设置为它们的类型。虽然这确实有效,但它看起来并不好,它需要所有函数的重复,我宁愿能够编写具有有意义参数的函数而不是模棱两可。
最小示例
#include <iostream>
#include <string>
#include <unordered_map>
#include <memory>
using namespace std;
void greetWorld() {
cout << "Hello World!" << endl;
}
void greetName(const string& name) {
cout << "Hello " << name << "!" << endl;
}
void printAddition(const int lhs, const int rhs) {
cout << "Addition: " << to_string(lhs + rhs) << endl;
}
template<typename... Args>
class DerivedFunction;
class BaseFunction {
public:
template<typename... Args>
void operator()(Args... args) const {
(*static_cast<const DerivedFunction<Args...>*>(this))(args...);
}
};
template<typename... Args>
class DerivedFunction : public BaseFunction {
public:
DerivedFunction(void(*function)(Args...)) {
this->function = function;
}
void operator()(Args... args) const {
function(args...);
}
private:
void(*function)(Args...);
};
template<typename... Args>
unique_ptr<DerivedFunction<Args...>> make_function(
void(*function)(Args...)
) {
return std::make_unique<DerivedFunction<Args...>>(function);
}
int main() {
unordered_map<string, unique_ptr<BaseFunction>> function_map;
function_map.insert({ "greetWorld", make_function(&greetWorld) });
function_map.insert({ "greetName", make_function(&greetName) });
function_map.insert({ "printAddition", make_function(&printAddition) });
cout << "Calling functions at compile time." << endl << endl;
(*function_map.at("greetWorld"))();
(*function_map.at("greetName"))("Foo"s);
(*function_map.at("printAddition"))(1, 2);
//cout << endl << "Calling functions at runtime." << endl << endl;
//string runtime =
// "greetWorld\n"
// "greetName string Foo\n"
// "printAddition int 1 int 2";
//
// todo: call functions
}
解决了。
如果您应用接受的解决方案,您可以像我想要的那样从文本中调用函数。这是示例 Tcp 服务器和客户端的新代码。客户端将函数名称和参数作为字符串发送到服务器。然后服务器执行这些。正是我想要的。
struct FunctionNameAndArguments {
string function_name;
vector<RPC> arguments;
};
FunctionNameAndArguments parseFunctionNameAndArguments(
const string& function_name_and_arguments_string
) {
istringstream ss(function_name_and_arguments_string);
FunctionNameAndArguments function_name_and_arguments;
// function name
ss >> function_name_and_arguments.function_name;
// arguments
auto& arguments = function_name_and_arguments.arguments;
while (!ss.eof()) {
string function_type;
ss >> function_type;
// integer
if (function_type == "int") {
int value;
ss >> value;
arguments.push_back(value);
}
// string
else if (function_type == "string") {
string value;
ss >> value;
arguments.push_back(value);
}
else {
throw exception("unknown argument type");
}
}
return function_name_and_arguments;
}
int main() {
unordered_map<string, RPCHandler> functions = {
{ "greetWorld", make_invoker(&greetWorld) },
{ "greetName", make_invoker(&greetName) },
{ "printAddition", make_invoker(&printAddition) }
};
char server;
cout << "Server? (y/n): " << endl;
cin >> server;
// server
if (server == 'y') {
// accept client
TcpListener listen;
listen.listen(25565);
TcpSocket client;
listen.accept(client);
size_t received;
// receive size of string
size_t size;
client.receive(&size, sizeof(size), received);
// receive function name and arguments as string
string function_name_and_arguments_string;
function_name_and_arguments_string.resize(size);
client.receive(
function_name_and_arguments_string.data(),
size,
received
);
// go through each line
istringstream lines(function_name_and_arguments_string);
string line;
while (getline(lines, line)) {
// parse function name and arguments
auto [function_name, arguments] = parseFunctionNameAndArguments(
line
);
// call function
functions.at(function_name)(
arguments
);
}
}
// client
else {
// connect to server
TcpSocket server;
server.connect("localhost", 25565);
// function calls string
const string function_calls =
"greetWorld\n"
"greetName string Foo\n"
"printAddition int 1 int 2";
size_t size = function_calls.size();
// send size of string
server.send(&size, sizeof(size));
// send function calls string
server.send(function_calls.data(), size);
}
}
解决方案
假设您有一个可用于 RPC 的类型列表(以 int 和 string 为例),我们可以将它们组合成一个RPC
类型并关联RPCHandler
如下:
using RPC = std::variant<int, std::string>;
using RPCHandler = std::function<void(std::vector<RPC>)>;
你想创建一个std::map<std::string, RPCHandler> dispatch
所以你可以做(给定一个std::vector<RPC> args
):
dispatch[command](args);
该地图可按如下方式构建:
void test0();
void test2(int, std::string);
std::map<std::string, RPCHandler> dispatch = {
{ "test0", make_invoker(test0) },
{ "test2", make_invoker(test2) },
};
wheremake_invoker
返回正确形状的 lambda。这个 lambda 的主体将函数指针、参数向量和 astd::index_sequence
传递给invoke_rpc
:
template<class... Arg>
RPCHandler make_invoker(void (*f)(Arg...)) {
return [f](std::vector<RPC> args) {
invoke_rpc(f, args, std::index_sequence_for <Arg...>{});
};
}
最后,依次invoke_rpc
使用std::get
每个参数将其转换为预期的类型。它通过并行扩展两个给定的模板参数包来做到这一点。直观地说,这将扩展为f(std::get<Arg0>(args.at(0), std::get<Arg1>(args.at(1))
与预期一样多的参数f
(因为索引序列具有相同的长度Args...
)。
template<class... Arg, std::size_t... I>
void invoke_rpc(void (*f)(Arg...), std::vector<RPC> args, std::index_sequence<I...>) {
f(std::get<Arg>(args.at(I))...);
}
如果向量太短,你会得到一个std::out_of_range
错误,如果有一个参数不匹配,你会得到一个std::bad_variant_access
. args
您可以通过在调用之前检查 的大小来改进错误处理f
,并使用std::holds_alternative
它来查看所有传递的值是否与其被禁止的类型匹配。
推荐阅读
- c++ - 如何在卡萨布兰卡为外部连接设置 listener() 设置?
- java - 类文件的版本错误
- c++ - 为什么当我声明复制初始值设定项被删除时,使用等于运算符的声明是错误的
- android - android studio 3.4.1 的 Jarsigner 问题
- java - 从下向上动画片段,在上一个片段之上
- regex - 正则表达式从选择中排除空格
- kubernetes - Is it possible to know if the node where a Kubernetes Pod is being scheduled is master or worker?
- c# - 使用 XElement 和 XNamespace C# 类创建 Xml 属性 json:Array
- ios - 我删除了 CFBundleDocumentType。但文件仍然连接
- tcl - 期望通过 ssh 写入远程文件