将元素添加到基于范围的for循环中的预分配向量是否合法?

Pau*_*zak 22 c++ stl visual-studio

我正在使用Visual Studio 2015 Update 1 C++编译器和此代码段:

#include <iostream>
#include <vector>

using namespace std;

int main()
{
  vector<int> v{3, 1, 4};

  v.reserve(6);

  for (auto e: v)
    v.push_back(e*e);

  for (auto e: v)
    cout << e << " ";

  return 0;
}
Run Code Online (Sandbox Code Playgroud)

发布版本运行正常,但调试版本会生成vector iterators incompatible错误消息.这是为什么?

在将基于范围的循环c ++ 11中的元素添加到向量之前将其标记为重复问题之前,请阅读我的答案 /sf/answers/2482748201/,其中包含相反的参数.

Sla*_*ica 17

根据文件:

如果new size()大于capacity(),那么所有迭代器和引用(包括过去的迭代器)都将失效.否则只有过去的结束迭代器无效.

它表示即使容量足够,过去的迭代器也会失效,所以我相信你的代码有UB(除非这个文档不正确,标准另有说明)


Yak*_*ont 16

您的代码显示未定义的行为,但它很棘手,往往只在调试版本中捕获.

执行a时v.push_back,如果size传递容量,则所有迭代器都将失效.你可以通过储备避免这种情况.

但是,即使您没有使容量增长,过去的迭代器仍然会失效.通常,迭代器失效规则不区分"迭代器的'值'将是垃圾/引用不同的对象"和"迭代器的'位置'不再有效".当任何一个发生时,迭代器被认为是无效的.由于结束迭代器不再是结束迭代器(它从引用到任何东西,在几乎每个实现中都引用某些东西),标准只是声明它是无效的.

这段代码:

for (auto e: v)
  v.push_back(e*e);
Run Code Online (Sandbox Code Playgroud)

大致扩展到:

{
  auto && __range = v; 
  for (auto __begin = v.begin(),
    __end = v.end(); 
    __begin != __end;
    ++__begin
  )
  { 
       auto e = *__begin;
       v.push_back(e*e);
  }
}
Run Code Online (Sandbox Code Playgroud)

v.push_back呼叫无效的__end迭代器,然后对进行比较,并调试建立正确的标志未定义行为是一个问题.调试MSVC迭代器非常小心失效规则.

发布版本执行未定义的行为,并且因为向量迭代器基本上是指针周围的薄包装器,并且指向过去结束元素的指针成为在没有容量溢出的推回之后指向最后一个元素的指针,它"作品".

  • 使其成为未定义的行为有好处吗?它迫使我使用指针或索引重写此循环,而不是使用更高级别的构造。最终结果是编译器从我的示例中生成的代码相同,但语言律师会认为这是非法的。这是STL设计的缺陷吗? (2认同)
  • 试图进入规范作者的思想,实现者可能希望有一个过去的结束迭代器,其值是一个哨兵,而不仅仅是一个指向内存中下一个点的指针.如果他们指定所有先前的过去的迭代器指向新的"push_back"值,那么这样的实现是不可能的.为什么,值得吗?我不知道.也许调试功能可能会发现将迭代器标记为过去是有价值的.规范编写者倾向于在未定义行为方面犯错,并在以后需要时定义它. (2认同)