std::string(size, '\0')与s.resize(size)之间的区别是什么?

8

与std::vector不同,std::string没有提供一个接受大小的一元构造函数:

std::string s(size); // ERROR

这两者之间有什么不同吗:

std::string s(size, '\0');

并且

std::string s;
s.resize(size);

在常见实现中,它们的性能如何?

resize()函数会将字符串初始化为全零字符还是保留未指定值?

如果是全零字符,是否有办法构造一个给定大小的字符串,但保留未指定值的字符?


1
@FirstStep:不,其中 size 是一些 size_t,且它是 > 0 的。 - Andrew Tomazos
调整字符串大小时,会将所有字符初始化为零还是保留未指定值?请参阅文档,例如此处 - davidbak
根据标准,resize(n),其中 n > size() 将使用类似于 charT() 初始化的字符来初始化字符串的其余部分。 - Alejandro
@AndrewTomazos 如果您不想进行值初始化,可以像这样指定分配器。但是,当然,类型就不再是std::string了... - Praetorian
这两者之间不应该有明显的性能差异,通常两者都应该达到最终字符串长度的线性水平。 - Chuanzhen Wu
显示剩余4条评论
4个回答

4

在 IT 技术中,有一个区别,例如在 std::string s(size, '\0'); 中,字符串所需的所有内存可以一次性分配。然而,在第二个示例中,如果 size 大于存储在小字符串优化中的字符数量,则可能需要执行额外的分配,尽管这是实现定义的,并且在符合 C++ 17 标准的实现中在这方面肯定不会更具性能表现。然而,第一个示例更加简洁,可能更具性能,因此可能更可取。调用 s.resize(size); 时,所有新字符都将使用 char 的默认构造函数(即 '\0')进行初始化。没有办法使用未指定的值初始化 string


6
为什么第二种情况会有第二次分配?在这两种情况下,如果 size 大于 SSO 缓冲区大小,都会进行一次分配。 - Praetorian
6
你是否考虑使用make_shared?因为与make_unique相比,它们应该没有区别。无论如何,我发现你假设默认构造std::string将一次性分配内存,这很不寻常,但是允许的。但仍然断言存在差异似乎是错误的。可能会有差异,但除非你认为初始化一些变量为0,然后再更改它们与一开始就将它们初始化为size之间有差异,否则大多数情况下不会有差异。 - Praetorian
4
string()现在是noexcept,这意味着它禁止分配内存(幸好)。 - Howard Hinnant
自 C++ 17 起,它是 noexcept 的。但我会再次更新我的答案,因为我之前不知道这一点,这将改善答案。感谢您发现了这个问题! - DeepCoder
@Praetorian:是的。然后呢?如果你不需要内存来实现默认状态,为什么要分配它呢?默认字符串构造函数也可以建立网络连接。但那当然也是愚蠢的。string(和vector)上的noexcept被放置在发送强烈信息的位置,表明此操作不会分配内存。我们没有为相同的原因装饰列表默认构造函数--它允许分配一个哨兵节点。 - Howard Hinnant
显示剩余4条评论

3
实际答案取决于具体实现,但我相当确定std::string s(size, '\0');更快。
std::string s;
s.resize(size);

根据std::string文档,字符串的默认构造函数会构造一个空字符串(大小为零且容量未指定)。这里我的理解是,实现可以自由地确定默认容量,可能在10-15个字符左右(完全是猜测)。
然后,在下一行,如果size大于当前capacity,你将重新分配内存(resize)以使用新的size。这可能不是你想要的!
如果你真的想知道确切的情况,可以在这两种方法上运行性能分析器。

虽然它的实现基于具体情况,但我不知道任何默认构造函数会分配内存的实现。它要么使用SSO并在堆栈缓冲区中具有小容量(无需分配),要么不使用SSO并且容量为零。C++17现在通过使默认构造函数noexcept来强制执行此操作。因此,在几乎所有实现中,实际上没有性能差异。 - ScottG

2

已经有一个很好的答案来自DeepCoder

然而,为了记录,我想指出字符串(就像向量一样)有两个不同的概念:

  • size():它是字符串中实际(即有意义的)字符的数量。您可以使用resize()更改它(您可以提供第二个参数以指定要用作填充字符的字符,如果它应该是其他字符而不是'\0'
  • capacity():它是分配给字符串的字符数。它至少是大小,但可以更多。您可以使用reserve()增加它。

如果您担心分配性能,我认为最好使用容量。大小应该保留给字符串中实际字符,而不是填充字符。

顺便说一下,更一般地说,s.resize(n)s.resize(n,char())相同。因此,如果您想以相同的方式在构造时填充它,可以考虑string s(n,char())。但是只要不使用T与字符不同的类型的basic_string<T>,您的'\0'就足够了。


1

调整大小不会使元素未初始化。根据文档: http://en.cppreference.com/w/cpp/string/basic_string/resize

s.resize(size) 将对每个附加字符进行值初始化。这将导致调整大小的字符串的每个元素都被初始化为'\0'

您需要测量特定C++实现的性能差异,才能真正决定是否有值得的差异。

在查看Visual C++生成的机器代码进行优化构建后,我可以告诉您两个版本的代码量相似。看起来违反直觉的是,resize() 版本对我来说测量速度更快。但仍应检查自己的编译器和标准库。


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