C++ 从序列化字节数组进行语义最正确且类型安全的构造?(c+;+;11)
考虑下面的c++11类,该类表示IPv4头结构,该结构应该可以从字节数组构造,而不考虑字节顺序C++ 从序列化字节数组进行语义最正确且类型安全的构造?(c+;+;11),c++,c++11,constructor,semantics,type-safety,C++,C++11,Constructor,Semantics,Type Safety,考虑下面的c++11类,该类表示IPv4头结构,该结构应该可以从字节数组构造,而不考虑字节顺序 #include <arpa/inet.h> #include <netinet/in.h> namespace Net { using addr_t = ::in_addr_t; #pragma pack(push, 1) struct ip_header_t { uint8_t ver_ihl; uint8_t tos; uint1
#include <arpa/inet.h>
#include <netinet/in.h>
namespace Net {
using addr_t = ::in_addr_t;
#pragma pack(push, 1)
struct ip_header_t {
uint8_t ver_ihl;
uint8_t tos;
uint16_t total_length;
uint16_t id;
uint16_t flags_fo;
uint8_t ttl;
uint8_t protocol;
uint16_t checksum;
addr_t src_addr;
addr_t dst_addr;
ip_header_t( const uint8_t* bytes, const bool ntoh = false ) {
auto o = (ip_header_t&)*bytes;
ver_ihl = o.ver_ihl;
tos = o.tos;
ttl = o.ttl;
protocol = o.protocol;
total_length = ntoh? ntohs(o.total_length) : o.total_length;
id = ntoh? ntohs(o.id) : o.id;
flags_fo = ntoh? ntohs(o.flags_fo) : o.flags_fo;
checksum = ntoh? ntohs(o.checksum) : o.checksum;
src_addr = ntoh? ntohl(o.src_addr) : o.src_addr;
dst_addr = ntoh? ntohl(o.dst_addr) : o.dst_addr;
};
};
#pragma pack(pop)
}
#包括
#包括
名称空间网{
使用addr\u t=::in\u addr\u t;
#pragma包(推送,1)
结构ip_头\u t{
国际人道主义法第8版;
uint8_t tos;
uint16_t总长度;
uint16_t id;
uint16_t flags_fo;
uint8_t ttl;
uint8_t协议;
uint16_t校验和;
地址src地址;
地址;
ip头(const uint8字节,const bool ntoh=false){
自动o=(ip_头&)*字节;
国际人道主义法=国际人道主义法;
tos=o.tos;
ttl=o.ttl;
协议=o.协议;
总长度=总长度?总长度:总长度;
id=ntoh?ntohs(o.id):o.id;
flags\u fo=ntoh?ntohs(o.flags\u fo):o.flags\u fo;
校验和=ntoh?ntohs(o.校验和):o.校验和;
src_addr=ntoh?ntohl(o.src_addr):o.src_addr;
dst_addr=ntoh?ntohl(o.dst_addr):o.dst_addr;
};
};
#布拉格语包(流行语)
}
我担心接受字节数组可能不是最安全或语义最正确的方法。将数组转换为结构本身似乎是一种非常C-ish的方法,缺乏类型安全性(更不用说边界检查)。要求调用方担心这一点并要求对实例进行常量引用是否更好?将字节数组强制转换到此类绝对不是正确的做法-正如您所提到的,不同系统上的字节顺序可能不同(这就是为什么构造函数中有一个
ntohs
)
类的位置完全取决于实体的角色和职责。如果看不到设计,就无法判断。一种表示原始二进制数据的类型,具有某种假定的布局:
template<typename T, size_t order>
struct serial_tag {};
模板
结构串行_标记{};
介绍一些表示磁盘上数据的预期类型和布局的名称:
typedef serial_tag<uint8_t , 0> ver_ihl_ser;
typedef serial_tag<uint8_t , 1> tos_ser;
typedef serial_tag<uint16_t, 2> total_length_ser;
...
typedef serial_tag<addr_t , 9> dst_addr_ser;
typedef序列标签版本;
typedef串行标签至服务器;
typedef序列标签总长度;
...
typedef串行标签dst地址;
一组串行_标签,可由其他代码操作:
template<typename... tags>
struct serial_pack {};
模板
结构串行_包{};
编写包含一个序列包的代码,并确保使用的每个序号都没有间隙
编写接受反序列化迭代器和串行_标记的代码,并在从串行_标记生成数据时推进该反序列化迭代器。这应该处理持久性
目标是用一种与元编程相适应的方式描述数据的原始数据布局,然后使用该布局信息将数据加载到C++结构中。
这是一个流式读取操作,其中反序列化迭代器(或范围)知道它是否对其大小有限制,并且知道您是否正确地按顺序读取元素(至少在调试中)
我不知道这是否值得,但它确实解决了你的担忧
这种方法的一个缺点是它违反了DRY,因为内存中的布局理论上可以用作描述序列化后原始字节布局的方法。相反,我们必须维护一组完全不同的数据来表示它。另一个好处是,这意味着我们的in-C++布局不必完全复制二进制布局。在我看来,最好的解决方案是提供一个复制构造函数,它可以处理字节顺序转换并依赖调用方进行转换 像这样:
/* copy constructor: */
ip_header_t( const ip_header_t& src, const bool ntoh = false )
: ver_ihl(src.ver_ihl),
tos(src.tos),
ttl(src.ttl),
protocol(src.protocol) {
total_length = ntoh? ntohs(src.total_length) : src.total_length;
id = ntoh? ntohs(src.id) : src.id;
flags_fo = ntoh? ntohs(src.flags_fo) : src.flags_fo;
checksum = ntoh? ntohs(src.checksum) : src.checksum;
src_addr = ntoh? ntohl(src.src_addr) : src.src_addr;
dst_addr = ntoh? ntohl(src.dst_addr) : src.dst_addr;
};
/* client code using byte array in network-order */
auto ip_header = Net::ip_header_t((Net::ip_header_t&)*(byte_array), true);
我也不是100%肯定我最喜欢这个解决方案。考虑到字节顺序交换与对象的构造并不严格相关,因此最好创建一个这样做的非成员函数。关注字段对齐和排序也可能不是类应有的责任。我认为这种方法通常很好……只要直接处理字节数组,就会得到“C-ish”实现。需要确保的一点是,结构是以打包的方式构建的:不幸的是,没有这方面的标准,但下面是一些示例。您总是可以对最终用户隐藏字节数组构造函数。对什么实例的常量引用?如果您建议调用者应该负责提供头结构,那么您的代码到底有什么意义?对结构本身实例的常量引用——类似于复制构造函数,但添加了bool参数。代码的目的是允许从网络字节顺序进行转换。我现在得到的另一个好处是,如果参数是常量引用,我还可以为那些不需要转换的成员使用初始化列表。@MSD:没错,我忽略了指定打包对齐方式。我更新了问题示例以反映正确的对齐方式。恐怕我不明白你的意思。在网络字节顺序与主机字节顺序相同的系统上,ntohX()不会对任何字节重新排序。这不仅符合类型安全的要求,而且还提供了显式声明序列化顺序的语义好处。对于运算符>>被重载的输入流,它可以对多字节类型重新排序,这又如何呢?
#include <arpa/inet.h>
#include <netinet/in.h>
namespace Net {
using addr_t = ::in_addr_t;
#pragma pack(push, 1)
struct ip_header_t {
uint8_t ver_ihl;
uint8_t tos;
uint16_t total_length;
uint16_t id;
uint16_t flags_fo;
uint8_t ttl;
uint8_t protocol;
uint16_t checksum;
addr_t src_addr;
addr_t dst_addr;
ip_header_t( const uint8_t* bytes, const bool ntoh = false ) {
auto o = (ip_header_t&)*bytes;
ver_ihl = o.ver_ihl;
tos = o.tos;
ttl = o.ttl;
protocol = o.protocol;
total_length = ntoh? ntohs(o.total_length) : o.total_length;
id = ntoh? ntohs(o.id) : o.id;
flags_fo = ntoh? ntohs(o.flags_fo) : o.flags_fo;
checksum = ntoh? ntohs(o.checksum) : o.checksum;
src_addr = ntoh? ntohl(o.src_addr) : o.src_addr;
dst_addr = ntoh? ntohl(o.dst_addr) : o.dst_addr;
};
};
#pragma pack(pop)
}