my_file << binary::u32le << my_int << binary::u16le << my_string;
将my_int写为无符号的32位整数,将my_string写为长度前缀的字符串(前缀为u16le.)要读回文件,可以翻转箭头.效果很好.然而,我在设计上遇到了一个障碍,我仍然围着它.所以,是时候问问了. (我们做了一些假设,例如8位字节,2s补码整数和IEEE浮点数.)
引擎盖下的iostream使用streambufs.这真是一个非常棒的设计 – iostream将’int’的序列化编码到文本中,并让底层的streambuf处理其余部分.因此,你得到了cout,fstreams,stringstreams等.所有这些,包括iostream和streambufs,都是模板化的,通常是在char上,但有时也作为wchar.但是,我的数据是一个字节流,最好用’unsigned char’表示.
我的第一次尝试是基于unsigned char来模拟类. std :: basic_string模板足够好,但streambuf没有.我遇到了一个名为codecvt的类的几个问题,我永远无法遵循unsigned char主题.这提出了两个问题:
1)为什么streambuf对此类事情负责?似乎代码转换不属于streambuf的职责 – streambufs应该采取流,并缓冲数据到/从它缓冲数据.而已.像代码转换一样高级的东西感觉它应该属于iostreams.
由于我无法使用模板化的streambuf来处理unsigned char,所以我回到char,只是在char / unsigned char之间传递数据.出于显而易见的原因,我试图尽量减少演员阵容.大多数数据基本上都是在read()或write()函数中结束,然后调用底层的streambuf. (并在此过程中使用强制转换.)读取功能基本上是:
size_t read(unsigned char *buffer,size_t size) { size_t ret; ret = stream()->sgetn(reinterpret_cast<char *>(buffer),size); // deal with ret for return size,eof,errors,etc. ... }
好的解决方案,糟糕的解
前两个问题表明需要更多信息.首先,查看了boost :: serialization等项目,但它们存在于更高级别,因为它们定义了自己的二进制格式.这更适用于较低级别的读/写,其中希望定义格式,或者已经定义了格式,或者不需要或不需要批量元数据.
其次,有人问过binary :: u32le修饰符.它是一个类的实例化,它具有所需的字节顺序和宽度,此刻可能是未来的签名.该流保存该类的最后传递的实例的副本,并在序列化中使用该副本.这是一个解决方法,我试图重载<<运算符因此:
bostream &operator << (uint8_t n); bostream &operator << (uint16_t n); bostream &operator << (uint32_t n); bostream &operator << (uint64_t n);
但当时,这似乎不起作用.我有一些模糊函数调用的问题.对于常数来说尤其如此,尽管你可以像一张海报建议的那样,将其演绎或仅仅声明为const< type>.我似乎记得有一些其他更大的问题.
解决方法
我的解决方案(只需要在一台机器上临时序列化数据,因此不需要解决字节顺序)基于这种模式:
// deducible template argument read template <class T> void read_raw(std::istream& stream,T& value,typename boost::enable_if< boost::is_pod<T> >::type* dummy = 0) { stream.read(reinterpret_cast<char*>(&value),sizeof(value)); } // explicit template argument read template <class T> T read_raw(std::istream& stream) { T value; read_raw(stream,value); return value; } template <class T> void write_raw(std::ostream& stream,const T& value,typename boost::enable_if< boost::is_pod<T> >::type* dummy = 0) { stream.write(reinterpret_cast<const char*>(&value),sizeof(value)); }
然后,我进一步为任何非POD类型(例如字符串)重载了read_raw / write_raw.请注意,只需要重载read_raw的第一个版本;如果您是use ADL correctly,则第二个(1-arg)版本可以调用稍后定义的2-arg重载以及其他名称空间.
写例子:
int32_t x; int64_t y; int8_t z; write_raw(is,x); write_raw(is,y); write_raw<int16_t>(is,z); // explicitly write int8_t as int16_t
阅读示例:
int32_t x = read_raw<int32_t>(is); // explicit form int64_t y; read_raw(is,y); // implicit form int8_t z = numeric_cast<int8_t>(read_raw<int16_t>(is));
它不像重载运算符那样性感,并且事情不容易在一条线上(我倾向于避免,因为调试断点是面向行的),但我认为它变得更简单,更明显,并且不多更冗长.