std::string是否保证不会自动释放内存?

33

按照标准,std::string重新分配较小字符串的空间时,是否保证不会自动释放已分配的内存?

换句话说:

std::string str = "Some quite long string, which needs a lot of memory";
str = "";
str = "A new quite long but smaller string"; // Guaranteed to not result in a heap allocation?

我问这个问题是因为我依赖它来避免堆碎片。


如果新字符串的分配量小于先前的分配量,则不会进行分配。如果新字符串需要比当前更多的分配,将进行重新分配。类似于std::vector。 - Samer Tufail
1
即使字符串“自发地归还内存”,也不足以避免堆碎片化。字符串使用分配器(默认情况下为类型为std::allocator<char>的对象,但可以更改)来分配和释放内存,分配器可能再次使用较低级别的机制(例如newdelete运算符的变体)来实际分配和释放。如果任何一个步骤选择不释放内存到较低级别层,则可能对堆碎片化产生影响。 - Peter
我之所以这样问是因为我依赖它来避免堆碎片化。这正是我为std::string编写自己的内存控制器的原因。 - Peter VARGA
2
如果您需要保证这种行为,您可以始终使用自己的定制分配器。 - doron
11
根据我的经验,如果我不得不说“我依赖于避免堆碎片”,那么现在是考虑确定精确低级需求并可能自行编写分配程序的好时机。 - Cort Ammon
显示剩余2条评论
4个回答

35

毫无任何保证。

[string.cons]/36 定义将 const char* 赋值给 std::string 为移动赋值,其定义为:

[string.cons]/32

basic_string& operator=(basic_string&& str)  noexcept(/*...*/)

效果:Move操作与序列容器相同,但是迭代器、指针和引用可能会失效。

这表明委员会让实现自由选择无效操作和更保守的操作。为了使事情更加清晰:

[basic.string]/4

引用、指针和迭代器引用到basic_­string序列的元素可能会被以下basic_­string对象使用所使得无效:

  • (4.1) 作为参数传递给任何以非const basic_­string引用作为参数的标准库函数。
  • (4.2) 调用非const成员函数,除了 operator[], at, data, front, back, begin, rbegin, end, 和 rend

std::string 接受一个分配器作为模板参数。如果您真的关心可能的堆碎片问题,您可以编写自己的分配器,其中一些启发式方法可能具有适合您需求的分配策略。

实际上,我知道的大多数实现在您提出的问题情况下不会重新分配内存。这可以通过测试和/或检查您的实现文档以及源代码来检查。


5
假设有人向您提供这样的保证,那会是多么令人不安。例如,如果您将《葛底斯堡演说》存储在一个字符串中,然后用“Hello World”替换它,那将是多么令人不满意。 - Cort Ammon

13

CPP Reference 指出,对 char* 类型的指针进行赋值时:

将字符串 s 指向的以 NULL 结尾的字符数组作为新内容替换现有内容,就好像执行 *this = basic_string(s) 一样,这涉及到调用 Traits::length(s)。

实际上,这个“就好像”简化为一个右值赋值操作,因此以下场景是完全可能的:

  1. 创建一个新的临时字符串。
  2. 该字符串通过对右值引用进行赋值来窃取其内容。

5
cppreference通常是可靠的,但引用的陈述意味着有保证的缓冲区替换,这是胡说八道。除此之外,它是一个很好的概念模型。但它只比标准的描述略微简单,引用在YSC的答案中,因此后者更可取。 - Cheers and hth. - Alf
6
@Cheersandhth.-Alf cppreference在解释标准[string.cons]时进行了改写。 - Caleth
5
真的吗?它说“好像”,在标准用法中,这总是指“关于可观察的副作用,除了那些由构造/破坏引起的一些副作用。” - bipll
@rustyx 可以任选一种方式。你可以将其概念化为暂时窃取现有的分配,然后归还它。 - Caleth
1
@rustyx 标准只是讨论了效果。鉴于 x = "hi"; 的效果与 x.assign("hi"); 的效果相同,实现应该对两者都采取高效的方式。例如,libstdc++ 就直接调用 assign 来实现其 operator=(const CharT*) - Barry

4
无论实际答案如何(即“没有保证”) - 您应该遵循以下原则:如果不明显必须这样,那么就不要假设它是这样的。在您的特定情况下 - 如果您想紧密控制堆行为,则可能根本不想使用std::string(也许;这取决于情况); 你可能不想使用默认分配器(同样是这样); 你可能想要记忆字符串; 等等。您绝对应该做的是少作假设,如有可能进行测量,并明确设计以确保满足您的需求。

2
如果您的字符串比较短(最多15或22个字节,具体取决于编译器/标准库),并且您正在使用较新的C++11或更高版本编译器模式,则您可能会从短字符串优化(SSO)中受益。在这种情况下,字符串内容不会单独分配在堆上。
此链接还包含许多有关常见实现和分配策略的详细信息。
但是,您示例中的两个字符串都太长了,无法使用SSO。

这是否得到标准的保证?单点登录(SSO)是否得到标准的保证? - pipe
@pipe 不是这样的,Paul在这句话:“你有可能从短字符串优化(SSO)中获益。”中暗示了什么。 - YSC
@YSC 好的,但问题相当具体,而且 OP 已经知道这很可能会发生,因为他依赖它,所以我不明白这实际上回答了什么。 - pipe
@pipe 我同意这个答案有点偏离主题,但它是在问题被提出后立即回答的。当时问题没有标记为 [tag:language-lawyer],之后才进行了编辑。这个答案回答了 OP 关心的一部分(内存碎片)并提供了有趣的见解。我已经为它投了赞成票。 - YSC
这并不是一个完整的答案,只是针对SSO情况的补充。我认为这个链接关于std::string性能的实际措施非常有趣。 - Paul Floyd

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