【问题标题】:Why doesn't range-based for loop modifiy container elements?为什么基于范围的 for 循环不修改容器元素?
【发布时间】:2019-07-03 14:38:52
【问题描述】:

我最近观察到修改自动迭代向量内的数据对我来说并没有产生正确的结果。例如,当我尝试对vector的vector元素进行排序时,有些元素没有排序但代码运行成功

vector<vector<int> > arr;
arr.push_back({38, 27});
for(auto v : arr)
{
    sort(v.begin(), v.end());
}

上述代码排序后的输出还是38,排序后的27。而当我排序为 sort(arr[0].begin(), arr[0].end()) 时,结果是正确的。我使用 gcc 编译。

【问题讨论】:

  • 为什么你认为结果是不确定的?
  • "为什么 C++ 编译器在结果确定未定义时允许这样做?" 有更简单的代码示例显示未定义行为,编译成功:int* pa; {int a = 5; pa = &amp;a;} *pa = 1;跨度>
  • 实际上,我遇到过一些不幸的情况,我试图在基于 for 循环的范围内修改容器本身,并得到未定义的结果,我认为同样的情况也适用于这段代码。既然我得到了答案,我将更改问题以使其具有通用性,而不是要求解决错误的假设。

标签: c++ c++11 auto


【解决方案1】:

您的 for 循环复制 v,然后对其进行排序。原件无动于衷。你要的是for (auto &amp;v : arr)

【讨论】:

    【解决方案2】:

    为什么修改自动迭代数据不是语法错误?

    因为无论变量是不是引用,修改变量的语法结构都很好。示例:

    int j = 0
    int& i = j; // i refers to j
    i = 42; // OK; j is modified
    
    
    int j = 0
    int i = j; // i is a copy of j
    i = 42; // OK even though i is not reference
            // j is not modified; only i is
    
    int j = 0
    auto i = j; // i is a copy of j
    i = 42; // OK; same as above except using auto deduced type
            // j is not modified
    
    std::vector<int> int_vec(10);
    for(int i : int_vec)
        i = 42; // OK; same as above except within a loop
                // the vector is not modified
    
    for(auto i : int_vec) // i is a copy
        i = 42; // OK; same as above except using both auto and a loop
                // the vector is not modified
    
    for(auto& i : int_vec) // i is a reference
        i = 42; // OK; elements of the vector are modified
    

    我最近才知道,当基于 for 循环的范围内的数据被修改时,结果是未定义的。

    您遇到了不正确的知识。在基于 for 循环的范围内修改变量不会产生未定义的结果。

    有些操作可能无法对正在迭代的范围执行,特别是那些使迭代器/对迭代范围元素的引用无效的操作。您可能对此感到困惑。

    您展示的程序具有明确定义的行为。但是,您可能打算对向量元素进行排序,但没有成功。为此,您必须引用向量的元素,而不是复制。这是通过使用引用来实现的:

    for( auto &v : arr )
              ^ this makes the variable a reference
    

    而当我排序为 sort(arr[0].begin(), arr[0].end()) 时,结果是正确的。

    下标运算符返回一个引用

    【讨论】:

    • 实际上这确实发生了几次,我试图修改我正在迭代的容器并得到了奇怪的结果。因此我添加了这个问题。反正现在很清楚了。
    【解决方案3】:

    这里没有什么是未定义的,也没有理由出现语法错误。

    您的循环按值迭代外部向量,然后对内部向量的本地副本进行排序。没意义,但不调皮。

    我最近才知道,当基于for循环的范围内的数据被修改时,结果是未定义的。

    你不能在结构上修改你正在迭代的东西,真的,因为这会破坏迭代。

    但这不是你所做的。即使您编写了auto&amp; v : arr,从而修改了内部向量的值,您仍然没有执行任何破坏外部向量arr 迭代的操作。

    不过,不要在循环内写 arr.clear()

    为什么修改自动迭代数据不是语法错误?

    即使您的程序有未定义的行为,这也绝不是语法错误,通常甚至不是运行时错误。

    但是如果有一条总括规则声明不能在基于范围的 for 循环内执行任何变异操作,请放心,该语言的语义可能会要求编译时错误阻止你这样做(比如你不能构建一个直接修改const int 的程序)。

    【讨论】:

      【解决方案4】:

      我最近才知道,当基于for循环的范围内的数据被修改时,结果是未定义的。

      你完全错了。如果您在迭代时修改容器本身(例如向其添加或删除元素),则它是未定义的行为,而不是您修改它包含的数据:

       std::vector<int> v( 10 );
       for( auto i : v ) v.push_back( 10 ); // UB as v is modified, new element inserted to it
      

      对比:

       std::vector<int> v( 10 );
       for( auto &i : v ) i = 123; // totally fine you modify elements inside vector
      

      您的其他问题毫无意义,因为它基于这个错误的假设。您没有观察到矢量变化中的数据的原因是不同的,并且已经得到了回答。但是,即使您修复了代码并使其修改了您的容器数据,它仍然可以。

      学究式注意:即使是 UB 来修改您迭代的容器的声明也太笼统了。例如,您可以删除 std::list 中的元素,同时对其进行迭代,但仍然避免使用 UB。但是当然这样的代码根本不应该写,因为它很容易出错,在这种情况下应该对迭代器范围进行迭代。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-10-31
        • 1970-01-01
        • 1970-01-01
        • 2012-04-29
        • 2014-02-08
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多