我一直在编写 iostreams 的二进制版本。它本质上允许您编写二进制文件,但让您可以控制文件的格式。示例用法:
my_file << binary::u32le << my_int << binary::u16le << my_string;
将 my_int 写为无符号 32 位整数,并将 my_string 写为长度前缀字符串(其中前缀为 u16le)。要读回文件,您可以翻转箭头。效果很好。但是,我在设计中遇到了一个问题,我仍然对此持观望态度。所以,是时候问了。(我们做了几个假设,例如 8 位字节、2s 补码整数和 IEEE 浮点数。)
iostreams,在引擎盖下,使用streambufs。这真的是一个很棒的设计——iostreams 将 ' int
' 的序列化编码为文本,并让底层的 streambuf 处理其余的。因此,您会得到 cout、fstreams、stringstreams 等。所有这些,无论是 iostreams 还是 streambufs,都是模板化的,通常在 char 上,但有时也作为 wchar。但是,我的数据是字节流,最好用' unsigned char
'表示。
我的第一次尝试是基于unsigned char
. std::basic_string
模板足够好,但streambuf
没有。我在一个名为 的类中遇到了几个问题codecvt
,我永远无法遵循这个unsigned char
主题。这提出了两个问题:
1)为什么一个streambuf要对这些事情负责?似乎代码转换超出了streambuf的职责——streambufs应该接受一个流,并缓冲数据到/从它。而已。像代码转换这样高级别的东西感觉应该属于 iostreams。
由于我无法让模板化的流缓冲区与 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>
. 我似乎记得还有其他一些更大的问题。