`std :: string :: begin()`/`std :: string :: end()`的迭代器失效?

时间:2015-02-26 17:01:49

标签: c++ c++11 iterator c++98 const-iterator

#include <string>
#include <iostream>

int main() {
    std::string s = "abcdef";

    std::string s2 = s;

    auto begin = const_cast<std::string const &>(s2).begin();
    auto end = s2.end();

    std::cout << end - begin << '\n';
}

此代码将begin() const的结果与end()的结果混合在一起。这些函数都不允许使任何迭代器无效。但是我很好奇end()要求不使迭代器变量begin无效的实际意味着变量begin是否可用于end

考虑一个C ++ 98,std::string的写时复制实现;非const begin()end()函数会导致复制内部缓冲区,因为这些函数的结果可用于修改字符串。因此begin上面的s开始对s2end()都有效,但使用非const s2成员会导致它不再对{{1}有效},生成它的容器。

上面的代码通过copy-on-write实现(例如libstdc ++)产生“意外”结果。 libstdc ++ produces another number而不是end - begins2.size()相同。

  • 是否导致begin不再是s2的有效迭代器,它是从中检索的容器,构成'使迭代器无效'?如果你看一下迭代器的要求,它们在调用.end()之后似乎都适用于这个迭代器,所以也许begin仍然有资格作为有效的迭代器,因此没有失效?

  • 上面的代码是否在C ++ 98中定义良好?在C ++ 11中,它禁止了写时复制实现吗?

根据我自己对规格的简要介绍,它看起来不够明确,因此可能无法保证begin()end()的结果可以一起使用,即使没有混合const和非const版本。

4 个答案:

答案 0 :(得分:6)

正如您所说,C ++ 11在这方面与早期版本不同。 C ++ 11中没有问题,因为所有允许写入时复制的尝试都被删除了。在pre-C ++ 11中,您的代码会导致未定义的行为;允许调用s2.end()使现有的迭代器无效(并且在g ++中完成,也可能仍然如此)。

请注意,即使s2不是副本,标准也会允许它使迭代器无效。实际上,C ++ 98的CD甚至使f( s.begin(), s.end() )s[i] == s[j]行为不明确。这只是在最后一刻才实现,并进行了更正,以便只有第一次调用begin()end()[]才能使迭代器失效。

答案 1 :(得分:2)

代码没问题:当迭代器存在危险或者保持对元素的引用时,非常需要CoW实现来取消共享。也就是说,当你有一些东西访问一个字符串中的元素并且它的副本冒险做同样的事情,即使用迭代器或下标运算符时,它必须是非共享的。它可以知道它的迭代器并根据需要更新它们。

当然,在并发系统中,如果没有数据竞争,几乎不可能完成所有这些,但是在C ++ 11之前没有数据竞争。

答案 2 :(得分:2)

从N3337(which is essentially identical to C++11)开始,规范读取([string.require] / 4):

  

引用basic_string序列元素的引用,指针和迭代器可能会被该basic_string对象的以下用法无效:
  [...]
   - 调用非const成员函数,除了operator [],at,front,back,begin,rbegin,end和rend。

至少在我阅读它时,这意味着不允许调用beginend来使任何迭代器无效。虽然没有直接说明,但我也认为这意味着不能调用const成员函数可以使任何迭代器无效。

这个措辞至少在n4296之前保持不变。

答案 3 :(得分:1)

C ++ 98 [lib.basic.string] / 5陈述:

  

引用basic_string序列元素的引用,指针和迭代器可能会被basic_string对象的以下用法无效:

     
      
  • 作为非成员函数swap()operator>>()getline()的参数。

  •   
  • 作为basic_string::swap()的论据。

  •   
  • 致电data()c_str()会员功能。

  •   
  • 调用非常量成员函数,operator[]()at()begin()rbegin()end()rend()除外

  •   
  • 除了返回迭代器的insert()erase()形式之外的任何上述用法之后,第一次调用非const成员函数operator[](),{{1 }},at()begin()rbegin()end()

  •   

由于rend()的构造函数是&#34;非const成员函数&#34;,它符合对非const s2的调用 - 这是最后一个这样的调用上面的项目符号 - 使迭代器无效。因此,程序没有按照C ++ 98定义行为。

我不会对C ++ 11发表评论,因为我认为其他答案清楚地解释了该程序在该环境中定义了行为。