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

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

本文关键字:std append string ostringstream c++ vs      更新时间:2023-10-16

在所有使用某种缓冲的示例中,我看到它们使用流而不是字符串。std::ostringstream 和 <<运算符与使用 string.append 有何不同。哪个更快,哪个使用更少的资源(内存)。

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

下面是一个示例:

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

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

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

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

使用输出流,您可以执行以下操作:

std::string content;
...
os << "Content-Length: " << content.length() << "rn";

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

更新:

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

更新 2:

好吧,我不清楚

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

构造

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

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

另外,请注意,您正在调用 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 << "rn";
    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("rn");
#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::ostringstream不一定作为字符的顺序数组存储在内存中。在发送这些 HTTP 标头时,您实际上需要具有连续的字符数组,并且可能会复制/修改内部缓冲区以使其顺序化。

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

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

附言:C++11 之前的std::string也不要求是顺序的,而几乎所有的库都将其实现为顺序的。您可以冒险或使用std::vector<char>。您需要使用以下方法来执行追加:

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

std::vector<char>对性能来说是最好的,因为它很可能更便宜,但与std::string和它们构建的实际时间相比,它可能并不重要。我做过一些类似于你正在尝试的事情,并与std::vector<char>一起去过。纯粹是因为逻辑原因;矢量似乎更适合这份工作。您实际上并不想要字符串操作之类的。此外,我后来做的基准测试证明它的性能更好,或者可能只是因为我没有很好地实现std::string操作。

在选择时,具有需求要求和最少额外功能的容器通常可以最好地完成工作。

使用 stream,您可以让您的类Myclass覆盖<<操作,以便您可以编写

MyClass x;
ostringstream y;
y << x;

对于追加,您需要有一个 ToString 方法(或类似的东西),因为您无法覆盖字符串的追加函数。

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

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