从字符串中有效地读取括号中两个以逗号分隔的浮点数,而不受全局语言环境

时间:2015-08-13 00:09:41

标签: c++ parsing std

我是一个库的开发人员,我们的旧代码使用sscanf()sprintf()从/向字符串读取/写入各种内部类型。我们遇到过使用我们库的用户的问题,并且其语言环境与我们基于XML文件的语言环境不同(“C”语言环境)。在我们的例子中,这导致从这些XML文件解析的值不正确,以及在运行时以字符串形式提交的值。区域设置可以由用户直接更改,但也可以在用户不知情的情况下进行更改。如果区域设置更改发生在另一个库中,例如GTK,这是​​一个错误报告中的“犯罪者”,就会发生这种情况。因此,我们显然希望从语言环境中删除任何依赖关系,以永久地摆脱这些问题。

我已经在float / double / int /的上下文中阅读了其他问题和答案,特别是如果它们被一个字符分隔或位于括号内,但到目前为止,我发现的建议解决方案对我们来说并不令人满意。我们的要求是:

  1. 不依赖于标准库以外的库。因此,使用boost中的任何内容都不是一种选择。

  2. 必须是线程安全的。这具体涉及可以全局更改的区域设置。这对我们来说真的很糟糕,因为我们库的一个线程可能会受到用户程序中另一个线程的影响,该线程也可能正在运行一个完全不同的库的代码。因此,直接受setlocale()影响的任何事情都不是一种选择。此外,由于线程中的竞争条件,在开始读/写之前设置区域设置并将其设置回原始值不是解决方案。

  3. 虽然效率不是最重要的优先级(#1&#2),但它仍然是我们关注的问题,因为字符串可能会在运行时频繁地读取和写入,具体取决于用户的程序。越快越好。

  4. 修改:另外请注意:boost::lexical_cast不保证不受语言环境的影响(来源:Locale invariant guarantee of boost::lexical_cast<>)。即使没有要求#1,这也不是解决方案。

    到目前为止,我收集了以下信息:

    • 首先,我看到的建议很多是使用boost的lexical_cast但不幸的是,这对我们来说根本不是一个选项,因为我们不能要求所有用户也链接到boost(并且由于缺乏现场安全,见上文)。我查看了代码,看看我们是否可以从中提取任何内容,但我发现它很难理解并且长度太大,而且很可能是性能提升者仍在使用依赖于语言环境的函数。
    • C ++ 11中引入的许多函数,例如std::to_stringstd::stodstd::stof等,都依赖于sscanf和sprintf的全局语言环境,这是非常的考虑到已经添加了std :: thread,不幸和我不可理解。
    • std::stringstream似乎是一般的解决方案,因为它在语言环境的上下文中是线程安全的,但一般来说如果保护正确的话。但是,如果它每次都是新的构造它可能很慢(良好的比较:http://www.boost.org/doc/libs/1_55_0/doc/html/boost_lexical_cast/performance.html)。我假设这可以通过配置和使用每个线程一个这样的流来解决,每次使用后清除它。但问题是,它并不像sscanf()那样轻松解决格式,例如:" { %g , %g } "
    例如,我们需要能够阅读的

    sscanf()模式是:

    • " { %g , %g }"
    • " { { %g , %g } , { %g , %g } }"
    • " { top: { %g , %g } , left: { %g , %g } , bottom: { %g , %g } , right: { %g , %g }"

    用stringstreams写这些似乎没什么大不了的,但阅读它们似乎有问题,特别是考虑到空白。

    我们应该在这种情况下使用std::regex还是这样矫枉过正? stringstreams是否是这项任务的一个很好的解决方案,或者根据上述要求,还有更好的方法吗?另外,在我的问题中我没有考虑过的线程安全和语言环境中是否有任何其他问题 - 特别是关于std :: stringstream的用法?

1 个答案:

答案 0 :(得分:5)

在您的情况下,class似乎是最好的方法,因为您可以独立于设置的全局区域设置来控制它的区域设置。但是格式化的阅读并不像stringstream那么容易。

从性能的角度来看,使用正则表达式进行流输入对于这种简单的逗号分隔读取来说是一种过度杀伤:在非正式基准测试中,它比scanf()慢10倍以上。

您可以轻松编写一个辅助类,以方便阅读您所枚举的格式。这里关于another SO answer的一般想法使用可以像以下一样简单:

sscanf()

如果您有兴趣,我会在一段时间之前写一篇文章。这里是full article with examples and explanation as well as source code。该类是70行代码,但大多数代码在需要时提供错误处理功能。它具有可接受的性能,但仍然比scanf()慢。