c++ - iostream 的二进制版本

标签 c++ binary iostream streambuf

我一直在编写 iostreams 的二进制版本。它本质上允许您编写二进制文件,但让您可以更好地控制文件的格式。用法示例:

my_file << binary::u32le << my_int << binary::u16le << my_string;

会将 my_int 写成无符号 32 位整数,将 my_string 写成长度前缀字符串(其中前缀为 u16le。)要读回文件,您需要翻转箭头。效果很好。然而,我在设计中遇到了一个障碍,我仍然对此持观望态度。所以,是时候问了。 (我们做了几个假设,例如 8 位字节、2s 补码整数和 IEEE float 。)

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 的责任——streambuf 应该接收一个流,并将数据缓冲到流中或从中缓冲数据。而已。像代码转换这样高级的东西感觉应该属于 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>。 .不过,我似乎记得还有其他一些更大的问题。

最佳答案

我同意合法化。我几乎需要做你正在做的事情,并查看了重载 <</>> , 但得出的结论是 iostream 并不是为了适应它而设计的。一方面,我不想必须对流类进行子类化才能定义我的重载。

我的解决方案(只需要在一台机器上临时序列化数据,因此不需要解决字节顺序问题)基于这种模式:

// 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));

它不像重载运算符那么吸引人,而且事情也不会那么容易地放在一行上(无论如何我倾向于避免这种情况,因为调试断点是面向行的),但我认为它变得更简单、更明显,并没有更冗长。

关于c++ - iostream 的二进制版本,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1150843/

相关文章:

c++ - unordered_map::insert vs operator []= 函数给出错误

c++ - C++ 结构的 "acceptable"有多少功能?

c++ - 我可以将 FlatBuffers 序列化/反序列化为 JSON 吗?

c++ - 如何将头文件编译成可执行文件?

java - 为什么 System.out/err 在 Java 中实现为字节流?

c++ - 从 iostream 读取父类(super class)的子类实例。 >> 运算符如何知道哪个子类?

c++ - boost::asio::deadline_timer renew 仍然调用处理函数

c++ - 将 Matlab 生成的二进制字符串转换为 float

以二进制格式组合两个文件

c++ - 是否可以使用 C++ 流类来缓冲从管道读取的数据?