在基于范围的for循环中使用转发引用有什么好处?


115

const auto&如果我要执行只读操作就足够了。但是,我碰到了

for (auto&& e : v)  // v is non-const

最近几次。这让我感到奇怪:

auto&或相比,在某些晦涩难懂的情况下使用转发引用是否有一些性能优势const auto&

shared_ptr疑似死角案件)


更新 在我的收藏夹中找到的两个示例:

遍历基本类型时使用const引用有什么缺点吗?
我可以使用基于范围的for循环轻松地迭代地图的值吗?

请集中讨论以下问题:为什么我要在基于范围的for循环中使用auto &&?


4
您是否真的经常看到它?
Lightness Races in Orbit

2
我不确定您的问题中是否有足够的背景信息可以让我了解您所看到的情况有多“疯狂”。
Lightness Races in Orbit

2
@LightnessRacesinOrbit长话短说:为什么我要auto&&在基于范围的for循环中使用?
阿里

Answers:


94

我看到的唯一好处是,当序列迭代器返回代理引用时,您需要以非常量方式对该引用进行操作。例如考虑:

#include <vector>

int main()
{
    std::vector<bool> v(10);
    for (auto& e : v)
        e = true;
}

这不会编译,因为vector<bool>::reference从返回的右值iterator不会绑定到非常量左值引用。但这将起作用:

#include <vector>

int main()
{
    std::vector<bool> v(10);
    for (auto&& e : v)
        e = true;
}

话虽如此,除非您知道需要满足这种用例,否则我不会以这种方式编写代码。也就是说,我不会无缘无故地这样做,因为它确实会使人们想知道您在做什么。如果我确实这样做了,那么就为什么添加评论是没有害处的:

#include <vector>

int main()
{
    std::vector<bool> v(10);
    // using auto&& so that I can handle the rvalue reference
    //   returned for the vector<bool> case
    for (auto&& e : v)
        e = true;
}

编辑

我的最后一个案例确实应该是一个有意义的模板。如果您知道循环始终在处理代理引用,则效果auto会和一样好auto&&。但是,当循环有时处理非代理引用,有时处理代理引用时,我认为auto&&它将成为首选解决方案。


3
另一方面,没有明显的缺点,是吗?(除了潜在的令人困惑的人,我个人认为这没有什么值得一提的。)
ildjarn 2012年

7
编写不必要使人感到困惑的代码的另一个术语是:编写没收的代码。最好使您的代码尽可能简单,但不要简单。这将有助于减少错误计数。话虽这么说,随着&&越来越熟悉,也许从现在起的5年后,人们就会期待有一个auto &&习惯用法(假设它实际上没有害处)。我不知道那是否会发生。但是,情人眼中的事物很简单,如果您不只是为自己写书,请考虑读者。
Howard Hinnant 2012年

7
我喜欢const auto&当我想编译器帮我查一下,我不小心修改序列中的元素。
Howard Hinnant 2012年

31
我个人喜欢auto&&在需要修改序列元素的通用代码中使用。如果不这样做,我会坚持下去auto const&
Xeo 2012年

7
@Xeo:+1正是由于像您这样的发烧友,不断地尝试并寻求更好的做事方式,C ++才得以不断发展。谢谢。:-)
霍华德·辛南特

26

在基于范围的-loop中使用通用引用auto&&通用引用for的优点是,您可以捕获所获得的内容。对于大多数类型的迭代器,您可能会为某种类型得到a T&或a 。有趣的情况是,取消迭代器的引用会临时产生一个问题:C ++ 2011有了宽松的要求,并不一定需要迭代器才能产生左值。通用引用的使用与以下参数中的转发相匹配:T const&Tstd::for_each()

template <typename InIt, typename F>
F std::for_each(InIt it, InIt end, F f) {
    for (; it != end; ++it) {
        f(*it); // <---------------------- here
    }
    return f;
}

该函数对象f可以治疗T&T const&以及T不同。为什么基于范围的for循环的主体应该不同?当然,要真正利用利用通用引用推断出类型的优势,您需要相应地传递它们:

for (auto&& x: range) {
    f(std::forward<decltype(x)>(x));
}

当然,using std::forward()表示您接受任何要从中移回的值。在我不知道的非模板代码中,这样的对象是否有意义(还好吗?)。我可以想象,使用通用引用可以为编译器提供更多信息以执行正确的操作。在模板化代码中,它不会对对象应该发生什么做出任何决定。


9

我几乎总是使用auto&&。为什么不用时就被边缘情况咬伤?它的键入时间也较短,我只是发现它更...透明。当您使用时auto&& x,您每次都会知道那x是完全正确的*it


29
我的问题是,如果满足,您会放弃const-ness 。这个问题问我在什么情况下会被咬。Dietmar或Howard尚未提及的极端案例是什么?auto&&const auto&
阿里

2
下面是如果你被它咬到的方式auto&&。如果要捕获的类型(例如)应在循环的主体中移动,并更改为以后的日期,以便解析为一种const&类型,则您的代码将静默继续运行,但您的移动将成为副本。此代码将具有欺骗性。但是,如果您将类型明确指定为r值引用,则更改容器类型的任何人都会收到编译错误,因为您确实确实希望移动这些对象而不希望复制这些对象……
cyberbisson

@cyberbisson我刚刚遇到了这个问题:如何在不显式指定类型的情况下强制执行右值引用?像for (std::decay_t<decltype(*v.begin())>&& e: v)什么?我猜有更好的方法了……
Jerry Ma

@JerryMa这取决于你有什么知道的类型。如上所述,auto&&将给出“通用引用”,因此除此以外的任何内容都将为您提供更具体的类型。如果v假定为a vector,则可以执行decltype(v)::value_type&&,这是通过operator*对迭代器类型进行取值来实现的。您也decltype(begin(v))::value_type&&可以检查迭代器的类型而不是容器。但是,如果我们对这种类型的了解auto&&
不足
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.