【问题标题】:Unusual behavior with auto while traversing a dynamic vector遍历动态向量时使用 auto 的异常行为
【发布时间】:2015-09-27 09:31:40
【问题描述】:

我正在遍历带有 auto 的向量(附加代码)。在遍历时,我还在后面附加了一些元素。我没想到会得到这样的输出。

#include <iostream>
#include <vector>
using namespace std;

vector <int> dynamic_vector;

void access( )
{
    for ( auto i : dynamic_vector ) {
        if ( i == 3 ) {
            dynamic_vector.push_back( 4 );
            dynamic_vector.push_back( 5 );
        }
        cout << i << endl;
    }
}

int main() {
    dynamic_vector.push_back( 1 );
    dynamic_vector.push_back( 2 );
    dynamic_vector.push_back( 3 );
    access( );
    return 0;
}

输出:

1
2
3

我期待从 1 到 5 的所有数字都会被打印出来。我无法理解如何使用 auto 进行遍历?

【问题讨论】:

  • Re 我希望从 1 到 5 的所有数字都会被打印出来——我希望自己有鼻恶魔。这是未定义的行为,鼻恶魔是调用未定义行为的典型结果。

标签: c++ c++11 vector iterator auto


【解决方案1】:

这叫Range-based for loop

6.5.4$1 基于范围的for语句[stmt.ranged]:

在每种情况下,基于范围的 for 语句都等价于

{
  auto && __range = range-init;
  for ( auto __begin = begin-expr,
             __end = end-expr;
        __begin != __end;
        ++__begin ) {
    for-range-declaration = *__begin;
    statement
  }
}

注意等效的伪代码__end(和__begin)只会在循环开始时设置一次。在您的情况下,在最后一次循环的 push_back 之后,迭代器可能无效。如果是,它们的增量和比较将取决于实现。这意味着,作为其中一种可能性,__end__begin 将保持不变,并且循环计数不会改变。

【讨论】:

  • 结果取决于迭代器的特定实现。例如,迭代器可以存储对向量对象的引用和元素的索引,而不是只存储一个指针。结束迭代器可能是一个特殊的 end_of_vector 迭代器。
  • @AndreyNasonov 是的,基本上这是一个实现依赖问题。
  • @songyuanyao 是实现定义的还是未定义的行为?我对标准的解读是它是 UB,但我不确定,因为我找不到无效迭代器的良好定义。我发现的最好的结果是无效的迭代器可能是单数迭代器,并且基本上支持赋值。
  • @Jens 该标准似乎不包含关于无效迭代器的比较和增量的显式表达式。所以正如 Andrey 所说,这取决于vector 及其迭代器的实现。如果迭代器被实现为指针(就像大多数实现对vector 所做的那样),根据这个answer,对无效指针的比较是实现定义的行为。
  • @songyuanyao §24.2.1.11 声明它可能是一个单数迭代器。在这种情况下,取消引用它是未定义的行为。我认为您不能假设它通常是实现定义的行为。
【解决方案2】:

除了songyuanyao的回答指出的问题外,您提供的代码是未定义的行为。首先,由于push_back,向量可能需要重新分配,然后所有迭代器都无效,因此增加循环变量是未定义的行为。

看着the documentation for push_back

如果新的 size() 大于 capacity() 则所有迭代器和 引用(包括过去的迭代器)无效。 否则只有过去的迭代器无效。

,我想说在基于范围的 for 语句中附加到向量在任何情况下都是未定义的行为,因为结束迭代器总是无效的。基于范围的 for 存储了初始 end()-iterator 的副本,并且此迭代器在第一个 push_back 之后失效。这与您的输出相匹配,因为它仍然指向三元素向量的原始末端。但是,您不应依赖此行为。

不幸的是,我在标准中找不到“无效迭代器”语义的严格定义。 §24.2.1.11 说无效的迭代器可能是单数的,但仅说明取消引用它们可能是未定义的行为。没有比较它们的语义,但考虑到向量的一种实现是使用内部存储之后的下一个内存地址,并且当向量重新分配时地址会发生变化,我会说循环是未定义的行为。

【讨论】:

    猜你喜欢
    • 2018-06-20
    • 1970-01-01
    • 2011-12-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多