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

Range based loop: get item by value or reference to const?

本文关键字:const 获取 引用 于范围 范围 循环      更新时间:2023-10-16

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

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时,它不是避免了每次迭代的冗余复制吗?

如果您不想更改项目,并且希望避免复制,那么auto const &是正确的选择:

for (auto const &x : vec)

建议你使用auto &的人是错误的。忽略它们。

重述如下:

  • 选择auto x当你想要使用副本。
  • 选择auto &x当你想要工作的原始项目,可以修改他们。
  • 选择auto const &x,当你想使用原始项目,不修改它们。

如果您有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)
    ....

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

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

为什么他们不建议const引用

因为他们给了一个错误的建议:-)你提到的是正确的。如果您只想观察对象,则不需要创建副本,也不需要对其进行非const引用。

编辑:

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

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

我将在这里相反地说,在基于范围的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()) { /* ... */ }