【问题标题】:Inserters and Extractors reading/writing binary data vs text插入器和提取器读取/写入二进制数据与文本
【发布时间】:2012-01-04 01:43:36
【问题描述】:

我一直在尝试阅读 iostream 并更好地理解它们。有时我发现它强调插入器 (<<) 和提取器 (>>) 旨在用于 textual 序列化。有几个地方,但这篇文章就是一个很好的例子:

http://spec.winprog.org/streams/

<iostream> 世界之外,有些情况是> 以类似流的方式使用,但不遵守任何文本约定。例如,当 Qt 的 QDataStream 使用时,它们会写入二进制编码数据:

http://doc.qt.nokia.com/latest/qdatastream.html#details

在语言级别,> 运算符属于您的项目以进行重载(因此 QDataStream 所做的事情显然是可以接受的)。我的问题是,对于那些使用 <iostream> 的人来说,使用 > 运算符来实现二进制编码和解码是否被认为是一种不好的做法。是否有(例如)任何期望,如果将文件写入磁盘上的文件,该文件应该可以使用文本编辑器查看和编辑?

是否应该始终使用其他方法名称并将它们基于read()write()?还是应该将文本编码仅视为与标准库 iostream 集成的类可以选择忽略的默认行为?


更新 这方面的一个关键术语问题似乎是区分“格式化”与“未格式化”的 I/O(与术语“文本”与“二进制”相对)。我发现了这个问题:

writing binary data (std::string) to an std::ofstream?

@TomalakGeret'kal 有一条评论说 “无论如何我都不想将

对该问题的公认答案是,只要您使用ios::binary 就可以了。这似乎支持了辩论中“没有任何问题”的一面......但我仍然没有看到任何关于这个问题的权威来源。

【问题讨论】:

  • “文本编码”是一个误导性术语。我会说,“格式化 I/O”更合适。
  • 无论你的框架做什么。
  • @KerrekSB 我对“文本编码”排除的内容比“格式化 I/O”排除的内容有更清晰的认识。如果我有一个包含 N 个 32 位整数的对象,那么使用 write() 为 N 输出 4 个字节,然后输出对应于值的 4*N 个字节……这仍然是“格式化”的吗?
  • 不,write() 输出未格式化的数据,因此整数的实际二进制(实现)表示按原样插入到输出流中。相比之下,“格式化”可能类似于创建整数 的文本表示,然后将(二进制表示的)文本插入到输出中。
  • @KerrekSB 与我试图解决的更大问题相比,我仍然无法判断您对“文本编码”短语的反对是严重还是次要问题。但是,与其在这里聊天,您是否介意阅读我的问题的字里行间并发布回复(尽您的能力)引用来源并解决它?我说的是一个可靠的“不要那样做”的论点,以帮助人们提出这样的建议:stackoverflow.com/q/1157469/211160

标签: c++ text iostream binary-data


【解决方案1】:

其实<<>>这两个操作符都是位移操作符;将它们用于 I/O 严格来说已经是一种误用。然而,这种误用与运算符重载本身一样古老,而今天的 I/O 是它们最常见的用法,因此它们被广泛认为是 I/O 插入/提取运算符。我很确定如果没有 iostreams 的先例,没有人会将这些运算符用于 I/O(尤其是具有可变参数模板的 C++11,解决了使用这些运算符解决 iostreams 的主要问题,在更清洁的方式)。另一方面,从语言的角度来看,重载的 operator<<operator>> 可以表示您想要的任何含义。

所以问题归结为可接受这些运算符的使用。为此,我认为必须区分两种情况:第一,新的重载作用于 iostream 类,第二,新的重载作用于其他类,可能设计为像 iostream 一样工作。

让我们首先考虑 iostream 类上的新运算符。让我从观察开始,即 iostream 类都是关于格式化的(以及相反的过程,可以称为“去格式化”;“lexing”恕我直言,这里不是很合适的术语,因为提取器不会确定类型,但只尝试根据给定的类型解释数据)。负责原始数据的实际 I/O 的类是流缓冲区。但是请注意,正确的二进制文件不是您只是转储内部原始数据的文件。就像文本文件(实际上更是如此)一样,二进制文件应该对其包含的数据具有明确指定的编码。特别是如果希望在不同系统上读取文件。因此,格式化输出的概念对于二进制文件也很有意义;只是格式不同(例如,为整数值写入预定数量的字节,最重要的字节在前)。

iostream 本身是用于处理文本文件的类,即处理其内容被解释为数据的文本表示的文件。许多内置行为为此进行了优化,如果在二进制文件上使用可能会导致问题。一个明显的例子是,默认情况下,在尝试任何输入之前会跳过空格。对于二进制文件,这显然是错误的行为。此外,使用语言环境对二进制文件没有意义(尽管有人可能会争辩说可能存在“二进制语言环境”,但我不认为为 iostreams 定义的语言环境为此提供了合适的接口)。因此,我会说为 iostream 类编写二进制 operator<<operator>> 是错误的。

另一种情况是您为二进制输入/输出定义一个单独的类(可能重用 streambuf 层来执行实际的 I/O)。由于我们现在谈论的是不同的类,所以上面的论证不再适用。所以现在的问题是:I/O 上的operator<<operator>> 应该被视为“文本插入/提取操作符”还是更普遍的“格式化数据插入/提取操作符”?标准类只将它们用于文本,但根本没有二进制 I/O 插入/提取的标准类,因此标准用法无法区分两者。

我个人会说二进制插入/提取与文本插入/提取足够接近,因此这种用法是合理的。请注意,您也可以制作有意义的二进制 I/O 操纵器,例如bigendianlittleendianintwidth(n) 来确定输出整数的格式。

除此之外,还可以将这些运算符用于不是真正的 I/O 的事情(您甚至不会想到使用 streambuf 层),例如从容器读取或插入容器。在我看来,这已经构成对运算符的滥用,因为那里的数据没有被转换成或转换成不同的格式。它只是存储在一个容器中。

【讨论】:

  • 感谢您的冗长回答。在您提出的观点中,听起来好像您在建议“正确”使用 iostream 插入器/提取器将生成一个可以跨不同平台架构或编译器实现传输的文件......但具有相同的含义。是否有可靠的来源将其定义为“格式化”在这种情况下的含义?如果是这样,<<>> 是否以某种方式独特地与该职责相关联(而不是仅使用 .read() 和 .write() 的项目)?
  • @HostileFork:实际上我想说一个正确的二进制文件具有明确定义的格式;然后您可以从另一个平台读取它的事实是自动的。还有一个使用.read().write() 的项目希望有一个明确定义的二进制格式。此规则的一个例外是有效用作交换的临时文件;这样的文件在当前进程中不存在,并且永远不会从另一个文件中读取,因此它很可能只包含内存转储。如果一个旨在编写二进制文件的工具可以帮助用户编写正确的二进制文件,这显然是一个好主意。
  • 奖励详细回复。我对“答案”仍然有些不安,我可能会推迟宣布这一点,也许会写我自己的,因为我仍然觉得这里的水有点浑。很难真正为想要掌握 iostream 的新手提供指导,因为没有人承诺“这是好的代码,这样做”或“这是不好的代码,不要这样做” .
  • @HostileFork:感谢您授予赏金。
  • 我仍然不确定我是否真的认为这个问题得到了回答,它可能没有答案。但我希望结束旧问题,所以我接受。如果您在these slides 上有任何 cmets,请随时制作。
【解决方案2】:

重载的运算符 >> 和 > 和

【讨论】:

  • 有趣的是,您将语言环境问题作为“格式化”定义中的一个重要区别提出。但我仍在试图准确了解“好”或“坏”的做法。您认为您可以找到或创建一些示例代码来说明对比吗?
【解决方案3】:

标准中 iostream 的抽象是文本的 格式化的数据流;不支持任何非文本格式。 那是 iostreams 的抽象。没什么不好的 定义一个不同的流类,其抽象是二进制格式, 但在 iostream 中这样做可能会破坏现有代码,而不是 工作。

【讨论】:

  • 我的问题是基于接受 <<>> 运算符属于您的项目以重载(因此 QDataStream 所做的事情是可以接受的)。我想我的问题是更多想要实现 iostream 插入器和提取器的人说,如果将文件写入磁盘上的文件,则该文件应该可以使用文本编辑器查看和编辑......
  • 对于 iostream 以外的流上的二进制格式使用 <<>> 没有问题。问题是改变它们在 iostream 上的含义。
  • 但是……谁说的?请注意我上面带有@KerrekSB 的cmets。他建议实现整数的write() 没有“格式化”......但是如果我纠正字节顺序并写出一个非常具体的可以跨平台工作的4字节模式怎么办?在规范或规范中,我们可以在哪里找到对象不应该使用 iostream 的这些操作符以这种方式序列化自己...?
  • @HostileFork 说 iostream 的作者。该标准的作者几乎遵循套件,并且实际上没有任何对非文本格式的支持。所有标准的>> 读取文本格式,都希望被文本包围。您可以在字符缓冲区中插入二进制格式,然后使用ostream::write 将其输出,但这并不是 iostreams 的真正设计目的。如果您基本上有一个文本流,其中插入了少量二进制数据,则它是合适的,但如果整个流是二进制的,则使用二进制流要好得多。
  • @JamesKanze 我很欣赏你所谓的“直觉论证”。这也是我观察到的……在交付时的 iostream 中没有二进制序列化方法,并且由于其他所有内容都是用文本完成的,因此似乎会因为其他方式而摇摆不定。我将 Qt 示例作为非 iostream 的先例,但我想知道 iostream 规范中是否有任何反例,例如在 boost 中? (基本上除了有问题的代码示例或使用 > 将二进制格式序列化为 iostream 的落后代码库之外,还有其他地方吗?)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-07
  • 2015-06-16
  • 1970-01-01
  • 2021-07-02
相关资源
最近更新 更多