为什么基于范围的循环不修改容器元素?

Why doesn't range-based for loop modifiy container elements?

本文关键字:修改 元素 循环 于范围 范围 为什么      更新时间:2023-10-16

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

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 编译。

你的 for 循环复制 v,然后对其进行排序。原件原封不动。你想要的是for (auto &v : arr).

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

因为它在语法上是可以修改变量的,无论变量是否是引用。 示例:

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()) 时,结果是正确的。

下标运算符返回引用

这里没有未定义的内容,也没有理由出现语法错误。

循环按值迭代外部向量,然后对内部向量的本地副本进行排序。这毫无意义,但并不顽皮。

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

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

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

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

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

即使你的程序有未定义的行为,这从来都不是语法错误,通常甚至不是运行时错误。

但是,如果有一个一揽子规则规定在基于范围的 for 循环中不能执行任何变异操作,请放心,该语言的语义可能会强制要求编译时错误来阻止您这样做(例如您无法构建直接修改const int的程序)。

我最近才知道,当基于 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。但是当然,根本不应该编写这样的代码,因为它很容易出错,在这种情况下应该在迭代器范围内进行迭代。