【问题标题】:Simplest way to mix sequences of types with iostreams?将类型序列与 iostream 混合的最简单方法?
【发布时间】:2010-03-24 10:52:34
【问题描述】:

我有一个函数void write<typename T>(const T&),它是根据将T 对象写入ostream 来实现的,还有一个匹配函数T read<typename T>(),它从istream 中读取一个T。我基本上使用 iostreams 作为纯文本序列化格式,这显然适用于大多数内置类型,尽管我还不确定如何有效地处理 std::strings。

我也希望能够写出一系列对象,例如void write<typename T>(const std::vector<T>&) 或基于迭代器的等价物(尽管在实践中,它总是与向量一起使用)。然而,虽然编写一个迭代元素并将它们写出的重载很容易做到,但这并没有添加足够的信息来允许匹配的读取操作知道每个元素是如何分隔的,这与我本质上是相同的问题有一个 std::string。

是否有一种方法可以适用于所有基本类型和 std::string?或者也许我可以摆脱 2 个重载,一个用于数字类型,一个用于字符串? (可能使用不同的分隔符或使用分隔符转义机制的字符串。)

编辑:我很欣赏当面对这样的问题时通常明智的倾向是说“你不想那样做”并提出更好的方法,但我真的很喜欢直接相关的建议我问什么,而不是你认为我应该问什么。 :)

【问题讨论】:

    标签: c++ templates standard-library iostream


    【解决方案1】:

    一个通用的序列化框架是困难,iostream库的内置特性真的不能胜任——即使是令人满意地处理字符串也是相当困难的。我建议您要么坐下来从头开始设计框架,忽略 iostream(然后成为实现细节),要么(更现实地)使用现有的库,或者至少使用现有的格式,例如 XML。

    【讨论】:

    • 在这种情况下,我不需要完整的序列化框架,只需要能够读写这些单独的类型和同构类型的序列即可。
    • @Kylotan 如果你想继续前进,我会坐下来想想你将如何处理字符串,这实际上是一种同构类型的序列。您可以稍后再担心数组等。
    • 幸运的是(或者不幸的是,取决于你如何看待它)我有一个可以工作的单个字符串的特殊情况 - 我可以简单地读取整个流并使用它。
    • 所以字符串必须是文件中的最后一件事,因为它会占用文件的其余部分?
    • 如果我在处理文件,那将是一个严重的问题。 :)
    【解决方案2】:

    基本上,您必须创建一个文件格式。当您受限于内置函数、字符串和它们的序列时,您可以使用空格作为分隔符,编写用" 包裹的字符串(转义任何" - 然后也转义\ - 出现在流中他们自己),并选择任何不用于流式传输内置类型作为序列分隔符。存储序列的大小也可能会有所帮助。

    例如,

    5 1.4 "a string containing \" and \\" { 3 "blah" "blubb" "frgl" } { 2 42 21 }

    可能是int (5)、float (1.4)、字符串 ("a string containing " and \")、3 个字符串序列 ("blah""blubb") 的序列化,和"frgl"),以及2个ints(4221)的序列。

    或者,您可以按照 Neil 的建议 in his comment 将字符串视为字符序列:

    { 27 'a' ' ' 's' 't' 'r' 'i' 'n' 'g' ' ' 'c' 'o' 'n' 't' 'a' 'i' 'n' 'i' 'n' 'g' ' ' '"' ' ' 'a' 'n' 'd' ' ' '\' }

    【讨论】:

    • 我不需要在一个读或写操作中混合类型,这样那部分就不是问题了。每个操作都确切地知道它将处理什么类型,所以我只需要处理可变长度问题。不幸的是,这种处理字符串的方法对我来说效果不佳,因为它破坏了人类的可读性并将其推向了任意二进制格式。
    • @Kylotan:好吧,如果我对 Mike 和我的 cmets 理解正确,那就更容易了,因为您不需要将序列包装在分隔符中。你只需要写42 21(或者2 42 21,如果你喜欢存储对象的数量)来存储2个整数。
    • 这仍然是由空格分隔的。这适用于数字类型,但不会扩展到通常包含空格的字符串。这就是我的问题的第 3 段所指的内容。
    • @Kylotan:这就是我在" 中引入字符串包装的原因。
    【解决方案3】:

    如果你想避免转义字符串,你可以看看 ASN.1 是怎么做的。对于您声明的要求来说,这太过分了:这些东西的字符串、基本类型和数组,但原则是流包含明确的长度信息。因此,无需逃避任何事情。

    对于一个非常简单的等价物,您可以将uint32_t 输出为“ui4”,然后输出 4 个字节的数据,将int8_t 输出为“si1”,然后输出 1 个字节的数据,IEEE 浮点数输出为“f4”, IEEE 加倍为“f8”,依此类推。对数组使用一些额外的修饰符:“a134ui4”后跟 536 字节的数据。请注意,需要终止任意长度,而有界长度(如以下整数中的字节数)可以是固定大小(ASN.1 超出您需要的原因之一是它对所有内容都使用任意长度)。一个字符串可以是a<len>ui1,也可以是s<len>:之类的缩写。阅读器确实很简单。

    这有明显的缺点:类型的大小和表示必须独立于平台,输出既不是人类可读的,也不是特别压缩的。

    尽管使用 ASCII 而不是算术类型的二进制表示,但您可以使其大部分可读和一个终止符,因为不需要字符转义),并且可以选择添加一个大的人类可见的分隔符,反序列化器会忽略该分隔符。例如,s16:hello, worlds12:||s12:hello, worlds16:hello, worlds12:s12:hello, world 更容易阅读。阅读时请注意,看起来像分隔符的序列实际上可能不是一个,并且您必须避免陷入陷阱,例如假设代码中间的 s5:hello|| 意味着有一个 5 个字符长的字符串:它可能是 @ 的一部分987654328@.

    除非您对代码大小有非常严格的限制,否则使用现成的通用序列化程序可能比编写专门的序列化程序更容易。使用 SAX 读取简单的 XML 并不困难。也就是说,每个人和他的狗都写了“终于,序列化器/解析器/任何东西,这将让我们再也不用手动编码序列化器/解析器/任何东西”,或多或少都取得了成功。

    【讨论】:

      【解决方案4】:

      您可以考虑使用boost::spirit,它简化了从任意输入流中解析基本类型的过程。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-01-21
        • 2015-06-05
        • 1970-01-01
        • 1970-01-01
        • 2010-10-31
        • 2011-03-16
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多