C++ std::string 追加 vs push_back()
Posted
技术标签:
【中文标题】C++ std::string 追加 vs push_back()【英文标题】:C++ std::string append vs push_back() 【发布时间】:2013-02-11 11:51:15 【问题描述】:这确实是一个仅出于我个人利益的问题,我无法通过文档确定。
我在 http://www.cplusplus.com/reference/string/string/ 上看到 append 很复杂:
“未指定,但通常在新字符串长度中达到线性。”
而 push_back() 具有复杂性:
“未指定;一般为摊销常数,但在新字符串长度上可达线性。”
作为一个玩具示例,假设我想将字符“foo”附加到字符串中。会
myString.push_back('f');
myString.push_back('o');
myString.push_back('o');
和
myString.append("foo");
完全一样吗?或者有什么不同吗?您可能认为 append 会更有效,因为编译器会知道将字符串扩展至指定字符数需要多少内存,而 push_back 可能需要在每次调用时保护内存?
【问题讨论】:
除了显而易见的(前者是三个函数调用,而后者只有一个),前者可能会导致三个重新分配。我个人更愿意使用myString += "foo";
,因为我认为它更“自然”,即使它与append
调用相同。
关键词:“最多”。虽然很遗憾他们没有说明append
的典型案例,但只说明了最坏的情况。
@JoachimPileborg:push_back
必须摊销恒定时间,因此我知道的每个实现的三个分配的可能性为零。大多数从一些合理的大小开始(大于 1 或 2)并以几何方式增长底层缓冲区(每次大小增加 1.5 或 2 倍)。
【参考方案1】:
我有同样的疑问,所以我做了一个小测试来检查这个(g++ 4.8.5 with C++11 profile on Linux, Intel, 64 bit under VmWare Fusion)。
结果很有趣:
推:19 附加:21 ++++:34这可能是因为字符串长度(大),但运算符 + 与 push_back 和 append 相比非常昂贵。
另外有趣的是,当操作符只接收一个字符(不是字符串)时,它的行为与 push_back 非常相似。
为了不依赖预先分配的变量,每个循环都定义在不同的范围内。
注意:vCounter 只是使用 gettimeofday 来比较差异。
TimeCounter vCounter;
string vTest;
vCounter.start();
for (int vIdx=0;vIdx<1000000;vIdx++)
vTest.push_back('a');
vTest.push_back('b');
vTest.push_back('c');
vCounter.stop();
cout << "push :" << vCounter.elapsed() << endl;
string vTest;
vCounter.start();
for (int vIdx=0;vIdx<1000000;vIdx++)
vTest.append("abc");
vCounter.stop();
cout << "append :" << vCounter.elapsed() << endl;
string vTest;
vCounter.start();
for (int vIdx=0;vIdx<1000000;vIdx++)
vTest += 'a';
vTest += 'b';
vTest += 'c';
vCounter.stop();
cout << "++++ :" << vCounter.elapsed() << endl;
【讨论】:
【参考方案2】:在 C++03 中(“cplusplus.com”的大部分文档都是为此编写的),由于允许库实现者为字符串。例如,如果一个字符被修改并且正在进行共享,则 COW 实现可能需要复制整个字符串。
在 C++11 中,COW 和绳索实现被禁止。您应该期望每个添加的字符的恒定摊销时间或添加到末尾附加到字符串的字符数的线性摊销时间。实现者可能仍然会用字符串做相对疯狂的事情(与 std::vector
相比),但大多数实现将仅限于“小字符串优化”之类的事情。
在比较push_back
和append
时,push_back
剥夺了可能用于预分配空间的潜在有用长度信息的底层实现。另一方面,append
要求实现遍历输入两次才能找到该长度,因此性能增益或损失将取决于许多未知因素,例如尝试之前的字符串长度追加。也就是说,差异可能非常非常非常小。为此请使用append
——它更具可读性。
【讨论】:
但是,如果您只想追加 1 个字符,则没有append(char)
重载。在这种情况下,可以使用push_back()
。
@rustyx 当然,但这将是一个不同的问题:)
您知道 C++11/14/17 标准是否曾严格要求 push_back
/ append
/ insert
对字符串的复杂性要求?如果不是,您确定大多数现有实现都如此友好吗? (我隐约记得过去在这些方面遇到过问题,但也许这只是一些 CoW 实施的结果。)
@Nemo:我不确定您可以要求什么“收紧”。据我所知,它们一直是按恒定时间摊销的。目前是 N4606 23.2.3 [sequence.reqmts] /16 eel.is/c++draft/sequence.reqmts#16
@BillyONeal:我的意思是相对于 C++03,其中字符串上的 push_back
等基本上没有复杂性保证(与向量不同)。我看到这已针对 C++11 中的字符串上的 push_back
进行了更正,并感谢您的参考。但据我所知,该标准并未对字符串的insert(end(),...)
或append()
施加任何复杂性要求。因此,从“严格符合”的角度来看,push_back
调用的序列可能比append
快 O(n) 倍。除非我错过了什么。【参考方案3】:
在此处添加更多意见。
我个人认为在从另一个字符串逐个添加字符时使用push_back()
更好。例如:
string FilterAlpha(const string& s)
string new_s;
for (auto& it: s)
if (isalpha(it)) new_s.push_back(it);
return new_s;
如果在此处使用append()
,我会将push_back(it)
替换为append(1,it)
,这对我来说不太可读。
【讨论】:
【参考方案4】:是的,由于您给出的原因,我还希望 append()
表现更好,并且在您需要附加字符串的情况下,使用append()
(或operator+=
)当然更可取(尤其是因为代码更具可读性)。
但标准规定的是操作的复杂性。即使对于append()
,这通常也是线性的,因为最终需要复制要附加的字符串的每个字符(以及可能的所有字符,如果发生重新分配)(即使使用memcpy
或类似内容也是如此)。
【讨论】:
附加字符串的每个字符都需要被复制。如果不需要调整底层内存块的大小,则不需要复制字符串的现有内容。以上是关于C++ std::string 追加 vs push_back()的主要内容,如果未能解决你的问题,请参考以下文章
VS错误C++:不存在从 "WCHAR [260]" 转换到 "std::basic_string<char," 的适当构造函数