问题 在基于范围的循环c ++ 11中向向量添加元素


我使用了C ++ 11标准提供的新的基于范围的for循环,我提出了以下问题:假设我们迭代了 vector<> 使用基于范围的 for,在迭代过程中,我们在向量的末尾添加了一些元素。那么,什么时候循环结束?

例如,请参阅以下代码:

#include <iostream>
#include <vector>
using namespace std;
int main() {
    vector<unsigned> test({1,2,3});
    for(auto &num : test) {
        cout << num << " ";
        if(num % 2)
            test.push_back(num + 10);
    }
    cout << "\n";
    for(auto &num : test) 
        cout << num << " ";
    return 0;
}

我用“-std = c ++ 11”标志测试了G ++ 4.8和Apple LLVM版本4.2(clang ++),输出是(对于两者):

1 2 3
1 2 3 11 13

请注意,第一个循环终止于原始向量的末尾,尽管我们添加了其他元素。似乎for-range循环仅在开始时评估容器结束。 事实上,这是范围的正确行为吗?它是由委员会指定的吗?我们能相信这种行为吗?

请注意,如果我们改变第一个循环

for(vector<unsigned>::iterator it = test.begin(); it != test.end(); ++it)

使迭代器无效并出现分段错误。


3764
2018-06-12 22:48


起源

虽然添加不是擦除,但这是重复的 在基于范围的for循环内擦除容器中的元素 因为那里的答案显示标准所说的范围 - for - end是在循环开始之前确定并保存的,并且该保存的值用于后续迭代。 - Kate Gregory
@KateGregory我认为这不是重复,因为删除当前元素将是所有容器的未定义行为,但添加元素只是未定义的行为 std::vector, std::deque, std::unordered_set 和 std::unorderd_map。 - David Brown
我似乎有这个问题,行为略有不同。不管怎样,谢谢。 - an_drade
@KateGregory这不是重复的。答案是不同的 - 见下面我的。 - Paul Jurczak


答案:


不,你不能依赖这种行为。修改循环内的向量会导致未定义的行为,因为修改向量时循环使用的迭代器无效。

基于循环的范围

for ( range_declaration : range_expression) loop_statement

基本上相当于

{
    auto && __range = range_expression ; 
    for (auto __begin = std::begin(__range),
        __end = std::end(__range); 
        __begin != __end; ++__begin) { 
            range_declaration = *__begin;
            loop_statement 
    }
}

修改向量时,迭代器 __begin 和 __end 不再有效和解除引用 __begin 导致未定义的行为。


14
2018-06-12 23:02



谢谢大卫。有趣的是,我的两段代码虽然基本相同,却得出了截然不同的结果。 - an_drade
实际上,你错过了(auto _begin = r.begin(); _begin!= r.end(); ++ begin)和(auto _begin = r.begin(),_ end = r.end()之间的区别。 ); _begin!= _end; ++ _ begin)(缓存r.end())和 那 这就是为什么你的程序给人的工作印象!迭代器指向旧版本的向量。将析构函数放入所包含的对象中,随之而来的是欢闹...... :-D - Massa
修改向量时,循环使用的迭代器无效 不必要。如果vector的容量足够大而不需要重新分配,那么所有迭代器都保持有效。 - Paul Jurczak


只要向量的容量足够大,就可以依赖此行为,因此不需要重新分配。这将保证调用之后所有迭代器和引用的有效性,而不是过去的迭代器 push_back()。这很好,因为 end() 在循环开始时只计算一次(参见 的std ::矢量::的push_back)。

在你的情况下,你必须增加容量 test 向量来保证这种行为:

vector<unsigned> test({1,2,3});
test.reserve(5);

编辑

根据对...的回应 将元素添加到基于范围的for循环中的预分配向量是否合法?,这是一个未定义行为的情况。使用gcc,clang和cl构建的发布版本运行正常,但使用cl(Visual Studio)构建的调试版本将引发异常。


0
2018-02-17 21:10