【发布时间】:2015-02-14 03:22:38
【问题描述】:
我正在编写一个与网络相关的课程。我的应用程序接收表单的网络消息
[uint8_t message id, uint8_t/uint16_t/uint32_t data ...]
我的类允许其用户为特定的消息 ID 注册回调。
由于不同的消息有多种不同的数据条目数量(数据条目仅限于 uint8_t、uint16_t 和 uint32_t),因此我决定使用 C++11 的可变参数模板来减轻重复代码的负担。
这是我想要做的伪代码(没有编译它并且怀疑它编译)
#include <arpa/inet.h>
#include <stdexcept>
using namespace std;
template<class ...T>
struct MessageHandler {
size_t size;
std::function<void(T...)> callback;
template<class Head, class... Tail>
void parseHelper(uint8_t *data)
{
if (sizeof(Head) == 1) {
uint8_t val;
memcpy(&val, data, sizeof(Head));
// set next unset argument to the value of val
callback = std::bind(callback, val);
data += sizeof(Head);
} else if (sizeof(Head) == 2) {
uint16_t val;
memcpy(&val, data, sizeof(Head));
val = ntohs(val);
// set next unset argument to the value of val
callback = std::bind(callback, val);
data += sizeof(Head);
} else if (sizeof(Head) == 4) {
uint32_t val;
memcpy(&val, data, sizeof(Head));
val = ntohl(val);
// set next unset argument to the value of val
callback = std::bind(callback, val);
data += sizeof(Head);
} else {
throw std::invalid_argument("We support only 1, 2 and 4 byte integers!");
}
// repeat for the rest of arguments
parseHelper<Tail...>(data);
}
template<class ...Empty>
void parseHelper(uint8_t *data)
{
// do nothing, terminating case of recursion
}
template<class ...T>
void parse(utin8_t *data)
{
// parse `data` into T... arguments and bind them into `callback`
parseHelper<T...>(data);
// at this point `callback` has all arguments binded from `data`
// invoke the callback
callback();
}
}
// <message id, callback-holding helper struct>
std::unordered_map<uint8_t, MessageHandler> myMap;
template<class...T>
void dummy(T&&...)
{
// a dummy, does nothing
}
template<class...T>
void addMessageHandler(uint8_t messageId, std::function<void<T... arg>> callback)
{
MessageHandler<arg> mh;
mh.size = 0;
// order of execution is undefined, but we don't care
dummy( (mh.size += sizeof(arg))... );
mh.callback = callback;
myMap[messageId] = mh;
}
void foo(uint16_t a, uint8_t b, uint16_t c, uint32_t d)
{
// do stuff with the parsed message
}
void bar(uint32_t a)
{
// do stuff with the parsed message
}
int main()
{
// register callbacks
addMessageHandler<uint16_t, uint8_t, uint16_t, uint32_t>(0, std::bind(&foo));
addMessageHandler<uint32_t>(1, std::bind(&bar));
...
// get message over the network
uint8_t messageId = some_network_library.read.first_byte();
MessageHandler mh = myMap[messageId];
uint8_t *data = some_network_library.read.bytes(mh.size);
// parses and calls the callback with parsed values
mh.parse(data);
return 0;
}
在 main 中,我们为消息 ID 注册回调,然后通过网络接收消息,获取适当的 MessageHandler,逐个解析 data 变量,将它们中的每一个附加到回调绑定中,当我们绑定所有内容时 - 调用回调。
所以,我关心的事情:
是否甚至可以有一个映射(或其他一些基于整数键结构值的数据结构,具有近似恒定的查找),其中值是模板结构,并且您希望将不同类型的结构存储在它? (即存储在地图中的值不是同质类型的)。
-
我需要什么才能使
parse和parseHelper函数正常工作?- 我不确定您是否可以像这样将值附加到 std::function
- 在
parse中调用回调后,如何解除绑定所有绑定值? (或者他们在通话后自动解除绑定?)
如何使这段代码工作?
如果有人能将我的伪代码修复成一个可以工作的伪代码,那就太好了,解释为什么我的代码不能工作以及它是如何修复的,但只是解释也非常有帮助!
【问题讨论】:
-
您可以做一些事情...我看到 JSON 解析器正在做的事情是拥有指向每种类型的成员或指针,然后拥有一个枚举成员来告诉您哪种类型是有效类型...或者您可以对 (void *) 进行良好的旧 c 样式转换
-
这究竟能解决什么问题?另外,我不希望代码知道有关特定类型的任何信息。用户在提供回调时定义类型,其余代码必须使用用户提供的任何类型。我将可能性限制为 1、2 和 4 字节整数类型只是为了简化解释和示例代码,但实际代码会有点不同,可能是任意类型。
-
@JohnBerger Boost.Fusion 结构为这个问题提供了一个非常优雅的解决方案。查看this 博客文章和此CppCon talk。但是,就目前而言,您的问题太广泛了。尽管如此:)
标签: c++ templates c++11 bind variadic-templates