【问题标题】:What is the advantage of using forwarding references in range-based for loops?在基于范围的 for 循环中使用转发引用有什么好处?
【发布时间】:2012-10-19 07:16:59
【问题描述】:

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

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

最近几次。这让我想知道:

auto&const auto& 相比,使用转发引用是否有可能在某些不明显的极端情况下获得一些性能优势?

shared_ptr 是不知名的角落案例的嫌疑人)


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

Any disadvantage of using const reference when iterating over basic types?
Can I easily iterate over the values of a map using a range-based for loop?

请专注于这个问题:我为什么要在基于范围的 for 循环中使用 auto&&?

【问题讨论】:

  • 真的“经常”看到它吗?
  • 我不确定你的问题中是否有足够的背景信息来衡量你看到它的地方有多“疯狂”。
  • @LightnessRacesinOrbit 长话短说:我为什么要在基于范围的 for 循环中使用 auto&&

标签: c++ performance for-loop c++11 move-semantics


【解决方案1】:

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

#include <vector>

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

这不会编译,因为从 iterator 返回的右值 vector&lt;bool&gt;::reference 不会绑定到非 const 左值引用。但这会起作用:

#include <vector>

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

话虽如此,除非你知道你需要满足这样的用例,否则我不会这样编码。 IE。我不会无缘无故地这样做,因为它确实让人们想知道你在做什么。如果我确实这样做了,那么在评论中加入关于原因的评论也没有什么坏处:

#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&amp;&amp; 一样有效。但是当循环有时处理非代理引用,有时处理代理引用时,我认为auto&amp;&amp; 将成为首选解决方案。

【讨论】:

  • 另一方面,没有明确的缺点,是吗? (除了可能令人困惑的人,我个人认为这不值得一提。)
  • 编写不必要地混淆人们的代码的另一个术语是:编写混淆代码。最好使您的代码尽可能简单,但不要更简单。这将有助于减少错误计数。话虽这么说,随着 && 变得更加熟悉,那么也许 5 年后人们会开始期待 auto&& 习语(假设它实际上没有害处)。我不知道这是否会发生。但在旁观者看来很简单,如果您不仅仅是为自己而写作,请考虑到您的读者。
  • 当我希望编译器帮助我检查我没有意外修改序列中的元素时,我更喜欢const auto&amp;
  • 我个人喜欢在需要修改序列元素的通用代码中使用auto&amp;&amp;。如果我不这样做,我会坚持auto const&amp;
  • @Xeo:+1 正是因为像你这样的爱好者,不断地尝试和推动更好的做事方式,C++ 才不断发展。谢谢你。 :-)
【解决方案2】:

auto&amp;&amp;universal references 与基于范围的for-loop 一起使用具有捕获所获内容的优势。对于大多数类型的迭代器,您可能会得到 T&amp;T const&amp; 对于某些类型的 T。有趣的情况是取消引用迭代器会产生一个临时的:C++ 2011 放宽了要求,迭代器不一定需要产生一个左值。使用通用引用匹配std::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&amp;T const&amp;T。为什么基于范围的for-loop 的主体应该不同?当然,要真正利用使用通用引用推断类型的优势,您需要相应地传递它们:

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

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

【讨论】:

    【解决方案3】:

    我几乎总是使用auto&amp;&amp;。为什么在不必要的情况下被边缘案例咬伤?打字也更短,我只是觉得它更……透明。当你使用auto&amp;&amp; x 时,你就会知道x 每次都是*it

    【讨论】:

    • 我的问题是,如果const auto&amp; 足够,您将放弃const-ness 和auto&amp;&amp;。这个问题询问我可以被咬的角落案例。迪特马尔或霍华德尚未提及的极端案例有哪些?
    • 如果你这样做使用auto&amp;&amp;,这是一种被咬的方法。如果您要捕获的类型应该在循环体中移动(例如),并更改为以后的日期以便解析为 const&amp; 类型,您的代码将默默地继续运行,但您的动作将会到来副本。这段代码将非常具有欺骗性。但是,如果您将类型明确指定为 r 值引用,那么无论谁更改了容器类型,都会出现编译错误,因为您真的希望这些对象被移动而不是被复制......
    • @cyberbisson 我刚刚遇到这个:如何在不明确指定类型的情况下强制执行右值引用?像for (std::decay_t&lt;decltype(*v.begin())&gt;&amp;&amp; e: v) 这样的东西?我想有更好的方法...
    • @JerryMa 这取决于你做什么对类型的了解。如上所述,auto&amp;&amp; 将提供“通用参考”,因此除此之外的任何内容都会为您提供更具体的类型。如果v 被假定为vector,你可以做decltype(v)::value_type&amp;&amp;,这就是我假设你想要的,通过在迭代器类型上获取operator* 的结果。您也可以使用decltype(begin(v))::value_type&amp;&amp; 来检查迭代器的类型而不是容器。但是,如果我们对类型的了解如此之少,我们可能会认为使用auto&amp;&amp; 会更清楚一点...
    猜你喜欢
    • 1970-01-01
    • 2016-04-01
    • 2021-01-21
    • 2015-01-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多