基于范围的循环:通过值或引用const获取项?

读了一些基于范围的循环的例子,他们提出了两种主要的方法

std::vector<MyClass> vec;


for (auto &x : vec)
{
// x is a reference to an item of vec
// We can change vec's items by changing x
}

for (auto x : vec)
{
// Value of x is copied from an item of vec
// We can not change vec's items by changing x
}

好。

当我们不需要更改vec项时,IMO,示例建议使用第二个版本(按值)。为什么他们不建议const引用的东西(至少我没有发现任何直接的建议):

for (auto const &x : vec) // <-- see const keyword
{
// x is a reference to an const item of vec
// We can not change vec's items by changing x
}

这样不是更好吗?当它是const时,它不避免在每次迭代中重复复制吗?

119817 次浏览

如果你既不想更改项,又想要避免进行复制,那么auto const &是正确的选择:

for (auto const &x : vec)

无论谁建议你使用auto &都是错误的。忽略它们。

以下是概要:

  • 当你想处理副本时,选择auto x
  • 选择auto &x当你想使用原始项目,并可能修改它们。
  • 选择auto const &x当你想使用原始项目,而不修改它们。

当不需要更改vec项时,示例建议使用第一个版本。

然后他们会给出错误的建议。

为什么他们不建议使用const引用呢

因为他们给出了错误的建议:-)你所说的是正确的。如果你只想观察一个对象,不需要创建一个副本,也不需要对它有一个非-const引用。

编辑:

我看到你链接的引用都提供了在int值或其他一些基本数据类型的范围内迭代的例子。在这种情况下,由于复制int并不昂贵,因此创建一个副本基本上等同于(如果不是比观察const &更有效的话)。

然而,对于用户定义的类型,通常不是这样。udt的复制成本可能很高,如果你没有创建副本的理由(比如在不改变原始对象的情况下修改检索到的对象),那么最好使用const &

如果你有std::vector<int>std::vector<double>,那么使用auto(带值复制)而不是const auto&就可以了,因为复制intdouble很便宜:

for (auto x : vec)
....

但如果你有一个std::vector<MyClass>,其中MyClass有一些重要的复制语义(例如std::string,一些复杂的自定义类等),那么我建议使用const auto&避免深度拷贝:

for (const auto & x : vec)
....

我将在这里相反地说,在基于范围的for循环中不需要auto const &。告诉我你是否认为下面的函数很傻(不是在它的目的上,而是在它的写作方式上):

long long SafePop(std::vector<uint32_t>& v)
{
auto const& cv = v;
long long n = -1;
if (!cv.empty())
{
n = cv.back();
v.pop_back();
}
return n;
}

在这里,作者创建了v的const引用,用于所有不修改v的操作。在我看来,这很愚蠢,同样的论点也可以用于使用auto const &作为基于范围的for循环中的变量,而不仅仅是auto &

我会考虑

for (auto&& o : range_expr) { ...}

for (auto&& o : std::as_const(range_expr)) { ...}

它总是有效的。

并小心可能的临时范围表达式陷阱。

在c++ 20中

for (T thing = foo(); auto& x : thing.items()) { /* ... */ }