c++ std::ostringstream vs std::string::append

Posted

技术标签:

【中文标题】c++ std::ostringstream vs std::string::append【英文标题】: 【发布时间】:2013-11-19 14:59:44 【问题描述】:

在所有使用某种缓冲的示例中,我看到他们使用流而不是字符串。 std::ostringstream 和

我知道的一个区别是您可以将不同的类型输出到输出流中(如整数),而不是 string::append 接受的有限类型。

这是一个例子:

std::ostringstream os;
os << "Content-Type: " << contentType << ";charset=" << charset << "\r\n";
std::string header = os.str();

std::string header("Content-Type: ");
header.append(contentType);
header.append(";charset=");
header.append(charset);
header.append("\r\n");

显然使用流更短,但我认为 append 返回对字符串的引用,因此可以这样写:

std::string header("Content-Type: ");
header.append(contentType)
  .append(";charset=")
  .append(charset)
  .append("\r\n");

你可以使用输出流:

std::string content;
...
os << "Content-Length: " << content.length() << "\r\n";

但是内存使用和速度呢?尤其是在大循环中使用时。

更新:

为了更清楚,问题是:我应该使用哪一个,为什么?是否存在一种首选的情况?对于性能和内存......好吧,我认为基准测试是唯一的方法,因为每个实现都可能不同。

更新 2:

嗯,我不清楚我应该从答案中使用什么,这意味着他们中的任何一个都可以完成这项工作,加上向量。 Cubbi 做了很好的基准测试,添加了 Dietmar Kühl,最大的不同是这些对象的构造。如果您正在寻找答案,您也应该检查一下。我会再等一下其他答案(查看以前的更新),如果我没有得到答案,我想我会接受 Tolga 的答案,因为他之前已经完成了使用向量的建议,这意味着向量应该减少资源消耗。

【问题讨论】:

Offtopic:您还应该寻找一个快速函数将整数转换为字符串/字符。 sprintf/itoa 在 Content-Length 的简单整数到十进制字符串转换方面表现不佳。 sprintf 可能会因为格式化选项而变慢,但为什么您认为 itoa 很慢? 我不应该在那里写 itoa。我的意思是 itoa 不应该是一个选项,因为它是非标准的。但我记得将其与这些进行比较:gist.github.com/anonymous/7179097 【参考方案1】:

std::ostringstream 不一定作为字符的顺序数组存储在内存中。在发送这些 HTTP 标头时,您实际上需要有连续的字符数组,这可能会复制/修改内部缓冲区以使其连续。

std::string 在这种情况下使用适当的std::string::reserve 没有理由比std::ostringstream 慢。

但是,如果您完全不知道必须保留的大小,std::ostringstream 可能会更快地追加。如果您使用std::string 并且您的字符串增长,它最终需要重新分配和复制整个缓冲区。最好使用一个std::ostringstream::str() 来一次使数据按顺序排列,否则会发生多次重新分配。

附: Pre-C++11 std::string 也不需要是顺序的,而几乎所有的库都将它实现为顺序的。您可以冒险或改用std::vector&lt;char&gt;。您需要使用以下内容进行追加:

char str[] = ";charset=";
vector.insert(vector.end(), str, str + sizeof(str) - 1);

std::vector&lt;char&gt; 对性能来说是最好的,因为它很可能构建起来更便宜,但与std::string 以及它们构建所需的实际时间相比,它可能并不重要。我已经做了类似于你正在尝试的事情,并且之前使用了std::vector&lt;char&gt;。纯粹是因为逻辑上的原因;矢量似乎更适合这项工作。您实际上并不想要字符串操作等。另外,我后来做的基准测试证明它的性能更好,或者可能只是因为我没有用std::string 很好地实现操作。

在选择时,具有满足您的需求和最少额外功能的容器通常可以做得最好。

【讨论】:

动态增长缓冲区的成本与直觉相反。 使用适当的储备 我同意,否则它意味着不断重新分配内存,因此会降低性能。尽管ostringstream 没有按顺序存储它(出于性能原因),但这并不意味着您不能使用str().c_str() 在连续缓冲区中获取它。 @NickSoft 因为向量是顺序的,你可以通过访问它的第一个元素来访问它的缓冲区:char const* data = &amp;vector[0]; @NickSoft 我认为任何标题都不会超过 2KB,这似乎是一个很好的保留价值。即使您连接了 100 万个客户端,它也只会使用 2GB 的 RAM,与假设只有 5~10% 的实际用户同时使用(当然这是一个原始假设,如果不是很便宜,数据库操作可能根本不存在)。您甚至可以每天记录一次统计数据并进行计算,以找到合适的大小以动态保留。 @NickSoft en.cppreference.com/w/cpp/container/vector :元素是连续存储的,这意味着不仅可以通过迭代器访问元素,还可以使用指向元素的常规指针的偏移量。这意味着指向向量元素的指针可以传递给任何需要指向数组元素的指针的函数。 (顺便说一下,我建议使用 cppreference.com 查找内容)【参考方案2】:

构造一个流对象是一个比构造一个字符串对象复杂得多的操作,因为它必须保持(并因此构造)它的std::locale 成员,以及其他需要维护的东西状态(但语言环境在很大程度上是最重的)。

附加类似:两者都维护一个连续的字符数组,当超出容量时都分配更多。我能想到的唯一区别是,当附加到流时,每次溢出都会调用一个虚拟成员函数(除了内存分配/复制,它无论如何都会主导溢出处理),operator&lt;&lt; 必须做一些额外的检查流状态。

另外,请注意,您正在调用 str(),它会再次复制整个字符串一次,因此根据您编写代码的目的,流示例会执行更多操作并且应该会更慢。

让我们测试一下:

#include <sstream>
#include <string>
#include <numeric>

volatile unsigned int sink;
std::string contentType(50, ' ');
std::string charset(50, ' ');
int main()

 for(long n = 0; n < 10000000; ++n)
 
#ifdef TEST_STREAM    
    std::ostringstream os;
    os << "Content-Type: " << contentType << ";charset=" << charset << "\r\n";
    std::string header = os.str();
#endif
#ifdef TEST_STRING
    std::string header("Content-Type: ");
    header.append(contentType);
    header.append(";charset=");
    header.append(charset);
    header.append("\r\n");
#endif
    sink += std::accumulate(header.begin(), header.end(), 0);
 

那是 1000 万次 重复

在我的 Linux 上,我得到了

                   stream         string
g++ 4.8          7.9 seconds      4.4 seconds
clang++/libc++  11.3 seconds      3.3 seconds

因此,对于这个用例,在这两个实现中,字符串似乎工作得更快,但显然两种方式都有很多改进(reserve() 字符串,将流构造移出循环,使用不'不需要复制来访问它的缓冲区等)

【讨论】:

你忘记处理类似std::ios_base::width @Slava 编辑在荣誉奖中作为流构造的额外有效负载:字符串的操作符 稍微更改设置以在循环外构建流并仅重置它 (os.str("")) 以有趣的方式更改数字:流现在在 gcc 上更快,但在 clang 上更慢。我得到 gcc/string=4.5s, gcc/stream=2.5s, clang/string=2.25s, clang/stream=4.1s: 很好地交叉;) 所以除非你每次都在构造流,它实际上与使用字符串相当。【参考方案3】:

使用流,您可以让您的类 Myclass 覆盖 &lt;&lt; 操作,以便您可以编写

MyClass x;
ostringstream y;
y << x;

对于追加,您需要有一个 ToString 方法(或类似的方法),因为您不能覆盖字符串的追加功能。

对于某些代码片段,请使用您觉得更舒服的任何内容。 将流用于大型项目,其中能够简单地流式传输对象很有用。

【讨论】:

但正如 Dieter Lücking 指出的那样,您可以使用 + 来附加字符串。您可以轻松覆盖 + 运算符。 是的,但不是 append 函数。如果您覆盖 + 运算符,您可能会遇到麻烦,因为没有覆盖所有命令,或者当编译器决定首先评估其他操作时。我建议不要覆盖 + 运算符,除非你的类是一些标量或向量值。【参考方案4】:

如果您担心速度,您应该分析和/或测试。从理论上讲,std::string::append 应该不会更慢,因为它更简单(流必须处理语言环境、不同的格式并且更通用)。但是,只有通过测试才能意识到一种或另一种解决方案的速度到底有多快。

【讨论】:

每种类型调用一个不同的函数,这个选择是在编译时做出的。我认为这方面根本不影响性能。 @Havenard 我有没有说别的?

以上是关于c++ std::ostringstream vs std::string::append的主要内容,如果未能解决你的问题,请参考以下文章

转载C++中替代sprintf的std::ostringstream输出流详解

std::ostringstream输出流详解

std::ostringstream 转std::string

如何清除 std::ostringstream 实例以便可以重用? [复制]

std::ostringstream用法详解

需要一个宏来从 std::ostringstream 和 << arg 列表创建 std::string