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

Posted

技术标签:

【中文标题】基于范围的循环:按值获取项目或引用 const?【英文标题】:Range based loop: get item by value or reference to const? 【发布时间】:2013-02-17 00:55:38 【问题描述】:

阅读一些基于范围的循环示例,他们提出了两种主要方法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 时,它不是避免了每次迭代中的冗余副本吗?

【问题讨论】:

【参考方案1】:

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

for (auto const &x : vec)

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

这里是回顾:

如果您想处理副本,请选择 auto x。 如果您想使用原始项目并可以修改它们,请选择 auto &amp;x。 如果您想使用原始项目并且不修改它们,请选择 auto const &amp;x

【讨论】:

感谢您的出色回答。我想还应该指出const auto &amp;x相当于你的第三选择。 @mloskot:它是等价的。 (“但语法相同”是什么意思?语法明显不同。) 缺少:auto&amp;&amp;,当您不想制作不必要的副本,不在乎是否修改它,只想工作。 如果您只使用 int/double 等基本类型,引用区别是否适用于副本? @racarate:我无法评论整体的速度,而且我认为没有人可以评论它,除非先对其进行分析。坦率地说,我不会将我的选择基于速度,而是代码的清晰度。如果我想要不变性,我肯定会使用const。不过,是auto const &amp;,还是auto const,差别不大。我会选择 auto const &amp; 只是为了更加一致。【参考方案2】:

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

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

为什么他们不建议 const 引用的东西

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

编辑:

我看到您链接的所有参考资料都提供了迭代一系列 int 值或其他一些基本数据类型的示例。在这种情况下,由于复制 int 并不昂贵,因此创建副本基本上等同于(如果不是更有效)观察 const &amp;

但是,一般情况下,用户定义的类型并非如此。复制 UDT 的成本可能很高,如果您没有理由创建副本(例如修改检索到的对象而不更改原始对象),那么最好使用 const &amp;

【讨论】:

【参考方案3】:

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

for (auto x : vec)
    ....

但是如果你有一个std::vector&lt;MyClass&gt;,其中MyClass 有一些非平凡的复制语义(例如std::string,一些复杂的自定义类等),那么我建议使用const auto&amp; 来避免深拷贝

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

【讨论】:

【参考方案4】:

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

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 &amp; 作为变量在基于范围的 for 循环中,而不仅仅是 auto &amp;

【讨论】:

@BenjaminLindley:所以我可以推断您也会在 for 循环中反对 const_iterator?如何确保在迭代容器时不会更改容器中的原始项目? @BenjaminLindley:如果没有const_iterator,为什么您的代码无法编译?让我猜猜,你迭代的容器是const。但是为什么是const 开头呢?您在某处使用const 来确保什么? 创建对象const 的优势就像在类中使用private/protected 的优势一样。它避免了进一步的错误。 @BenjaminLindley:哦,我忽略了这一点。那么在这种情况下,实现也是愚蠢的。但是如果那个函数没有修改v,IMO 的实现会很好。如果循环从不修改它所遍历的对象,那么在我看来,对const 有一个引用是正确的。不过我明白你的意思。我的是循环代表一个代码单元,就像函数一样,在那个单元内没有需要修改值的指令。因此,您可以将整个单元“标记”为 const,就像使用 const 成员函数一样。 所以你认为基于范围的const &amp; 是愚蠢的,因为你写了一个不相关的虚构例子,一个虚构的程序员cv-qualification 做了一些愚蠢的事情? ...好的,那么【参考方案5】:

我会考虑

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

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

它总是有效的。

并提防可能的Temporary range expression 陷阱。

在 C++20 中你有类似的东西

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

【讨论】:

你能补充一下为什么更喜欢auto&amp;&amp;而不是auto&amp;吗? @LyndenShields auto&amp;&amp; 可以绑定到所有内容。 auto&amp; 不会绑定到右值,如果range_expression 返回右值,则不会工作。 range_expression返回右值的合理/现实例子吗? 好问题。我还没有任何例子。

以上是关于基于范围的循环:按值获取项目或引用 const?的主要内容,如果未能解决你的问题,请参考以下文章

按值传递比通过 const 引用传递更快的经验法则?

按值返回和通过 const 引用传递时避免临时构造

我在项目中使用Const

为啥向量被视为按值传递,即使它是通过 const 引用传递的?

复制赋值运算符应该通过 const 引用还是按值传递?

如何通过引用或值返回智能指针(shared_ptr)?