5
#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;非常量begin()end()函数会导致复制内部缓冲区,因为这些函数的结果可用于修改字符串。所以begin上面开始对sand都有效s2,但是使用非常量end()成员导致它不再对s2生成它的容器有效。

上面的代码使用写时复制实现(例如 libstdc++)产生“意外”结果。libstdc++不是end - begin与 相同,而是产生另一个数字s2.size()

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

  • 上面的代码在 C++98 中定义得很好吗?在 C++11 中,哪个禁止写时复制实现?

从我自己对规范的简要阅读来看,它似乎没有指定,因此可能无法保证begin()and的结果end()可以一起使用,即使没有混合 const 和非 const 版本。

4

4 回答 4

6

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

请注意,即使s2不是副本,标准也会允许它使迭代器无效。事实上,C++98 的 CD 甚至做出了类似f( s.begin(), s.end() )s[i] == s[j]未定义的行为。这只是在最后一分钟才实现的,并进行了更正,以便只有第一次调用begin(), end()or[]会使迭代器无效。

于 2015-02-26T17:19:09.857 回答
2

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

当然,在并发系统中,几乎不可能在没有数据竞争的情况下完成所有这些工作,但在 C++11 之前,没有数据竞争。

于 2015-02-26T17:12:40.617 回答
2

从 N3337 开始(本质上与 C++11 相同),规范为 ([string.require]/4):

引用 basic_string 序列元素的引用、指针和迭代器可能会因该 basic_string 对象的以下用途而失效:
[...]
- 调用非常量成员函数,除了 operator[]、at、front、back、开始、重新开始、结束和结束。

至少正如我所读到的,这意味着调用beginorend不允许使任何迭代器无效。虽然没有直接说明,但我也认为这意味着对const成员函数的调用不会使任何迭代器无效。

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

于 2015-02-26T17:12:56.077 回答
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()返回迭代器之外,第一次调用非常量成员函数operator[]()at()begin()rbegin()end()rend()

由于 的构造函数s2是一个“非常量成员函数”,因此它符合对非常量的调用s2.end()——根据上面最后一个项目符号的第一个这样的调用——使迭代器无效。因此,该程序没有根据 C++98 定义的行为。

我不会评论 C++11,因为我认为其他答案清楚地解释了程序在该上下文中定义了行为。

于 2015-02-26T21:00:47.287 回答