如何清空stringstream变量?

587

我已经尝试过几种方法了,

std::stringstream m;
m.empty();
m.clear();

两者都不起作用。

10个回答

923
对于所有标准库类型,成员函数empty()是一个查询而不是一个命令,即它的意思是“你是否为空?”,而不是“请丢弃你的内容”。 clear() 成员函数是从 ios 继承而来,用于清除流的错误状态,例如,如果文件流的错误状态设置为 eofbit(文件结束),则调用 clear() 将将错误状态设置为 goodbit(无错误)。
要清除stringstream的内容,请使用:
m.str("");

是正确的,尽管使用:

m.str(std::string());

从技术上讲,这种写法更加高效,因为你避免了调用接受const char*参数的 std::string 构造函数。但现在的任何编译器都应该能够在两种情况下生成相同的代码,所以我建议选择哪种更易读的方式。


136
当你忘记使用"clear()"这一部分时会发生什么,具体情况请见以下链接:https://dev59.com/mHE85IYBdhLWcg3wSxgv - galath
8
我认为在C++中,m.str()和m.str("")是两个不同的函数。m.str()调用了一个没有参数的函数,而m.str("")会调用接受const char*参数的函数。m.str()可能被实现为一个返回字符串的获取函数,而m.str("")可能被实现为一个设置函数。 - Dinesh P.R.
4
正如Galath所说,除了m.str("")清空字符串流之外,添加m.clear()也非常重要。否则,如果在某一点上将字符串流填充为空字符串,则可能会出现问题。 - Sputnik
7
哦,这真是直观易懂。就像 C++ 的其他所有东西一样! - sunny moon
1
这是我在Stackoverflow上遇到的最糟糕的被接受和点赞的答案。在我的std库实现中,此重置后stringstream无法使用。只需使用jerron的答案中的m = std :: stringstream();即可。 - Niceman
显示剩余7条评论

74

你可以一行代码中清除错误状态并清空stringstream

std::stringstream().swap(m); // swap m with a default constructed stringstream

这实际上将m重置为默认构造状态,这意味着它实际上删除了字符串流分配的缓冲区并重置了错误状态。下面是一个实验性的证明:

这将有效地重置m为一个默认构造的状态,这意味着它实际上删除了字符串流分配的缓冲区并重置了错误状态。以下是一个实验性的证明:

int main ()
{
    std::string payload(16, 'x');
    
    std::stringstream *ss = new std::stringstream; // Create a memory leak
    (*ss) << payload;                              // Leak more memory
    
    // Now choose a way to "clear" a string stream
    //std::stringstream().swap(*ss); // Method 1
    //ss->str(std::string());        // Method 2
    
    std::cout << "end" << std::endl;
}

演示

当使用地址检查器编译演示时,会显示内存使用情况:

=================================================================
==10415==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 392 byte(s) in 1 object(s) allocated from:
    #0 0x510ae8 in operator new(unsigned long) (/tmp/1637178326.0089633/a.out+0x510ae8)
    #1 0x514e80 in main (/tmp/1637178326.0089633/a.out+0x514e80)
    #2 0x7f3079ffb82f in __libc_start_main /build/glibc-Cl5G7W/glibc-2.23/csu/../csu/libc-start.c:291

Indirect leak of 513 byte(s) in 1 object(s) allocated from:
    #0 0x510ae8 in operator new(unsigned long) (/tmp/1637178326.0089633/a.out+0x510ae8)
    #1 0x7f307b03a25c in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::reserve(unsigned long) (/usr/local/lib64/libstdc++.so.6+0x13725c)
    #2 0x603000000010  (<unknown module>)

SUMMARY: AddressSanitizer: 905 byte(s) leaked in 2 allocation(s).

如果你问我,这很陡峭。为了保存仅16个字节的有效负载,我们花费了905个字节……字符串流并不是玩具。内存分配分为两部分:

  • 构建的字符串流(392字节)
  • 有效负载所需的额外缓冲区(513字节)。多余的大小与流选择的分配策略有关,对于<= 8个字节的有效负载,可以使用初始对象内部的块。

如果你启用方法1(即此答案中显示的方法),则可回收多余的513(有效负载)字节,因为该流实际上已被清除

如果您像评论或其他答案中建议的那样启用方法2,则可以看到我们退出时所有905个字节都在使用中。

在程序语义方面,人们可能只关心流“看起来”和“表现”为空,就像vector::clear会使容量保持不变但将向用户显示空向量一样(当然,向量在这里只需花费16个字节)。考虑到字符串流所需的内存分配,我可以想象这种方法通常会更快。 本答案的主要目标是实际清除字符串流,因为它带来的内存消耗不是闹着玩的。根据您的用例(流的数量,它们保存的数据,清除的频率),您可以选择最佳方法。

最后请注意,很少有用处只清除流而不清除错误状态和所有继承状态。此答案中的一行代码可同时完成两者。


6
与这里所有其他答案相比,这是最有效和最优雅的方法。然而,std::stringstream::swap 是一个 C++11 特性,这个解决方案在之前的 C++11 编译器中无法工作。 - 101010
5
GNU g++ v4.8 仍然缺少某些功能,请参见 https://dev59.com/ZIDba4cB1Zd3GeqPLf0x。 - Joachim W
9
交换操作比移动赋值更好的原因是什么? - Deduplicator
6
这太低效了。当我想要重新使用原始的电子表格时,它会为我交换一个空的电子表格。 - Zhang
5
我认为使用m.str({})的效率会比调用std::stringstream().swap(m)更高,正如张的评论所说。简单解释一下:当你调用m.str({})时,我预计它会重复使用第一个操作期间分配的一些内存来加速下一个操作。而当你调用std::stringstream().swap(m)时,我希望任何在第一个操作期间分配的内存都会被转移给你刚创建的临时对象,从而失去了那种效率。不过,在实践中进行比较将是有趣的。 - Arthur Tacca
显示剩余11条评论

45

无论使用哪个编译器,这应该是最可靠的方法:

m=std::stringstream();

3
我个人认为这种方法更好,因为 m.str(""); 会导致我的 stringstream 一直被卡在那个空值上,无论我尝试了什么。但是使用这种方法,我就没有那个问题了。 - gelatine1
3
我遇到了同样的问题,对我来说 mm.clear(); mm.str(""); 起了作用。(如果有 C++11,则使用 swap 更好)。 - hochl
4
并不适用于所有情况。这将每次重新分配缓冲区,而mm.str("")则不会。 - Shital Shah
3
我使用stringstream对象的主要用途是保留一个本地线程的stringstream对象,以防止不必要地实例化stringstream - 实例化新的stringstream对象会复制全局环境对象 - 理论上这很快,只涉及增加一个原子计数,但在我处理的并发级别下常常令人沮丧。 - Spacemoose
1
use of deleted function ‘std::basic_stringstream<char>& std::basic_stringstream<char>::operator=(const std::basic_stringstream<char>&) - Rodrigo Gurgel
显示剩余4条评论

39
m.str("");

似乎工作正常。


3
这可以通过 .clear() 方法来清除,该方法在 OP 中有说明。 - Dave Lugg

16

我总是关注它:

{
    std::stringstream ss;
    ss << "what";
}

{
    std::stringstream ss;
    ss << "the";
}

{
    std::stringstream ss;
    ss << "heck";
}

1
这比清除“ stringstream ”更好吗? - Robur_131

14

我的看法:

这个方法在xcode和dev-c++中对我有效。我有一个程序是以菜单的形式展示,如果用户按请求反复执行该程序,它将填充一个stringstream变量,该变量在代码第一次运行时工作良好,但下一次用户运行相同的代码时将不会清除stringstream。但是,以下两行代码最终在每次填充字符串变量之前清除了stringstream变量。(尝试了2个小时并进行了谷歌搜索),顺便说一句,仅使用每行代码无法解决问题。

//clear the stringstream variable

sstm.str("");
sstm.clear();

//fill up the streamstream variable
sstm << "crap" << "morecrap";

4

还有很多其他的答案"可以"工作,但它们经常做不必要的拷贝或重新分配内存。

  1. 交换流意味着您需要丢弃其中一个流,浪费内存分配。对于分配默认构造的流也是一样的,

  2. 通过stringstream::strstringbuf::str将字符串分配给字符串缓冲区中的字符串可能会丢失已由字符串分配的缓冲区。

清除字符串流的规范方式如下:

void clear(std::stringstream &stream)
{
   if (stream.rdbuf()) stream.rdbuf()->pubseekpos(0);
}

流缓冲区中获取数据大小的规范方法是:
std::size_t availSize() (const std::stringstream& stream)
{
   if (stream.rdbuf())
      return std::size_t(
         stream.rdbuf()->pubseekoff(0, std::ios_base::cur, std::ios_base::out));
   else
      return 0;
}

将数据从流复制到其他预先分配的缓冲区,然后清除它的标准方法是:

std::size_t readAndClear(std::stringstream &stream, void* outBuf, std::size_t outSize)
{
   auto const copySize = std::min(availSize(stream), outSize);
   if (!copySize) return 0; // takes care of null stream.rdbuf()

   stream.rdbuf()->sgetn(outBuf, copySize);
   stream.rdbuf()->pubseekpos(0); // clear the buffer

   return copySize;
}

我打算让这个答案成为权威的回答。语言专家们,欢迎提供意见。


4
我点赞了这个答案,但后来发现它实际上并没有将当前大小设置为零,所以旧内容仍然可见:https://godbolt.org/z/erP8dMcfK - 4LegsDrivenCat

0
@Azeem的回答应该被视为这里最佳的解决方案。一方面,他的回答没有涉及到问题提问者具体的词语,但另一方面,我认为它解决了问题提问者的意图。引用@Azeem的代码片段:
{
    std::stringstream ss;
    ss << "what";
}

使用作用域上下文的好处:
- 重复使用变量是一种代码异味,所以尽可能避免这样做。 https://maximilianocontieri.com/code-smell-107-variables-reuse - 当进入上下文作用域时,您可以确保有一个正确初始化的新实例。
- 当退出上下文作用域时,保证实例被正确销毁,并且所有内存被回收。
- 它不需要知道stringstream是如何实现的(缓冲区、指针、索引等)。如果您发现需要知道一个类的实现方式,那么要么这个类的接口设计得很差,要么您只是错误地使用了这个类——这使得您的代码设计得很差。
- 它依赖于stringstream的构造函数和析构函数来执行正确的操作(经过全面测试和调试的代码),而不是依赖您刚刚编写的代码来执行正确的操作。
- 它易于输入、易于阅读、易于理解:每个上下文只需两个大括号。
缺点:
你必须学习上下文/块/作用域的概念。https://en.cppreference.com/w/cpp/language/scope。好了,你学完了。
看起来每个上下文都需要额外的CPU周期来创建一个新的stringstream,然后再销毁它。不过没关系,你的整个程序都会使用额外的CPU周期。在你经过实证证明你的程序没有足够的CPU周期之前,不要计算周期(甚至不要考虑它们)。

-1

这是一个概念性问题。

Stringstream 是一个流,因此它的迭代器是向前的,不能返回。在输出 stringstream 中,您需要使用 flush() 来重新初始化它,就像在任何其他输出流中一样。


3
flush()函数与其关联的存储设备进行同步。这不等同于重新初始化。 - Clearer

-14

这些在 GNU C++ 中不会丢弃 stringstream 中的数据

    m.str("");
    m.str() = "";
    m.str(std::string());

以下代码可以清空stringstream:

    m.str().clear();

7
我不太确定这会起作用,因为和bernhardrusch的方法一样存在问题。 .str()函数返回的是一个副本,清除该副本并不会有任何作用。 - Verdagon
1
此解决方案不适用于Microsoft Visual C++。 - Zak
不正确。清除操作应该在从流返回的字符串上进行,而不是在流本身上进行。 - Joey Carson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接