字符串自追加时反向迭代器的有效性保障问题
问题描述
我需要生成一个由原字符串与其反转字符串拼接而成的新字符串,请问以下代码是否能保证正常运行?
auto pq = [](std::string &s){ s.reserve(2*s.size()); s.append(s.rbegin(), s.rend()); };
已知reserve会合理设置capacity,但调用append时使用反向迭代器,是否会导致这些迭代器失效?
参考标准条款
我的C11标准副本(与C17草案语言表述一致)中,§[string.capacity]节规定:
void reserve(size_type res_arg=0);
10. 成员函数reserve()是一条指令,告知basic_string对象计划进行的大小变更,以便其相应地管理存储分配。
11. 效果:调用reserve()后,capacity()将大于或等于reserve的参数。[注:调用reserve()时传入小于当前capacity()的res_arg参数,实际上是一个非强制性的收缩请求;传入res_arg <= size()的参数,则是一个非强制性的收缩至合适大小的请求。——结束注]
12. 抛出异常:若res_arg > max_size(),则抛出length_error异常。227
227) reserve()会使用allocator_traits::allocate(),该函数可能抛出相应的异常。
而§[string.append]节规定:
basic_string& append(const charT* s, size_type n);
11. 要求:s指向一个至少包含n个charT元素的数组。
12. 抛出异常:若size() + n > max_size(),则抛出length_error异常。
13. 效果:函数将this所控制的字符串替换为一个长度为size() + n的字符串,其前size()个元素是原this所控制字符串的副本,剩余元素是s的前n个元素的副本。
14. 返回值:*this。
解答
放心,这段代码完全可以保证正常运行,不会出现迭代器失效的问题,原因很明确:
首先,s.reserve(2*s.size())提前为字符串预留了足够的存储空间——调用后字符串的capacity()至少是原长度的两倍,而此时size()还是原字符串的长度。这意味着后续的append操作不需要扩容,因为总长度(原长度+反转后的长度)刚好等于2*s.size(),完全在预留的容量范围内。
那反向迭代器为什么不会失效?迭代器失效最常见的原因是容器发生内存重新分配(比如扩容时需要把旧内存的内容拷贝到新内存,旧迭代器就指向无效地址了),但这里我们已经提前搞定了容量,append过程中不会触发任何内存重新分配。而且,s.rbegin()和s.rend()指向的是原字符串的元素范围——append是在原字符串的末尾追加内容,原字符串的前半部分会被完整保留,这些元素的内存位置根本没动,反向迭代器指向的原字符串末尾到开头的位置自然也不会失效。
结合你贴的标准条款再确认下:
reserve的效果保证了容量足够,所以append不会触发扩容,避免了迭代器失效的核心诱因。append的效果明确说明会保留原字符串的前size()个元素,再追加新内容,原元素的内存位置稳定,反向迭代器的指向一直有效。
举个实际例子:假设原字符串是"hello",reserve(10)后容量至少是10,size()还是5。调用append(s.rbegin(), s.rend())后,会把"olleh"追加到后面,最终得到"helloolleh"——整个过程中"hello"的内存位置没变化,反向迭代器指向的o、l、l、e、h也都在原来的位置,完全没问题。
内容的提问来源于stack exchange,提问作者jxh

