最语义正确和类型安全的构造从序列化字节数组?(c++ 11)
Most semantically correct and type-safe construction from serialized byte array? (c++11)
考虑下面的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;
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)
}
我担心接受字节数组可能不是最安全或最语义正确的方法来做到这一点。将数组强制转换为结构本身似乎是一种非常像c语言的方法,缺乏类型安全性(更不用说边界检查了)。要求调用者担心这一点并要求对实例的const引用会更好吗?
将字节数组强制转换为这个类绝对不是正确的事情-正如您提到的,字节顺序可能在不同的系统上不同(这就是为什么在构造函数中有一个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;
一组serial_tags,可以被其他代码操作:
template<typename... tags>
struct serial_pack {};
编写接受serial_pack的代码,并确保每个序数都在使用中,没有间隔。
编写接受反序列化迭代器和serial_tag的代码,并在从serial_tag生成数据的同时推进反序列化迭代器。这应该处理端序
目标是以一种适合元编程的方式描述数据的原始数据布局,然后使用此布局信息将数据加载到c++结构体中。
这是一个流读操作,反序列化迭代器(或range)知道它的大小是否有限制,并且知道你是否正确地按顺序读取元素(至少在调试中)。
我不知道这是否值得,但它确实解决了你的问题。
这种方法的缺点是它违反了DRY,因为在理论上,内存布局可以用作描述序列化后的原始字节布局的方法。相反,我们必须维护一组完全不同的数据来表示它。作为一个有利的方面,这意味着我们在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++
- 将字符串转换为无符号字符数组/字节数组
- 检查彩虹数组(检查数组的反向样式是否与自身匹配)
- 如何创建对齐的数组 c++ 数组?
- 查找数组中数组元素的重复出现?
- C++释放带有子数组的数组的内存分配
- 返回一个数组,该数组包含数组中的数量,该数组数量较小或等于给定数组中的元素
- 通过键盘输入字符串数组和 int 数组的数组大小
- 如何在C 中创建类似于Python的Numpy数组的数组
- 无法获取数组内数组的总和
- C4838 警告,包含常量字符* 数组的数组初始化
- 连接数组,数组 1 内存消失C++
- 如何在C++中创建二维数组的数组
- 获取字符数组C++数
- 2D数组中数组下标的类型"int[int]"无效
- C 模板类动态数组的数组到数组
- 结构数组的数组
- 如何制作具有动态大小的数组?动态数组的一般用法(可能还有指针)
- 对象的数组-STD ::数组 - 构造函数初始化问题
- 使用SSE内部函数将布尔数组(8字节布尔)转换为int或char