【问题标题】:Any disadvantage of using const reference when iterating over basic types?迭代基本类型时使用 const 引用有什么缺点吗?
【发布时间】:2012-10-14 23:42:27
【问题描述】:

我发现自己最近越来越多地使用 C++11,在过去我会使用迭代器的地方,我现在尽可能使用 range-based for loops

std::vector<int> coll(10);
std::generate(coll.begin(), coll.end(), []() { return rand(); } );

C++03:

for (std::vector<int>::const_iterator it = coll.begin(); it != coll.end(); ++it) {
   foo_func(*it);
}

C++11:

for (auto e : coll) { foo_func(e); }

但是如果集合元素类型是模板参数呢? foo_func() 可能会被重载以通过 const 引用传递复杂(= 复制昂贵)类型,并通过值传递简单类型:

foo_func(const BigType& e) { ... };
foo_func(int e) { ... };

我在使用上面的 C++03 风格的代码时没有考虑这么多。我会以同样的方式进行迭代,因为取消引用 const_iterator 会产生一个 const 引用,所以一切都很好。但是使用 C++11 基于范围的 for 循环,我需要使用 const 引用循环变量来获得相同的行为:

for (const auto& e : coll) { foo_func(e); }

突然间我不确定了,如果auto 是一个简单类型(例如实现引用的幕后指针),这是否不会引入不必要的汇编指令。

但是编译一个示例应用程序证实了简单类型没有开销,而且这似乎是在模板中使用基于范围的 for 循环的通用方式。如果不是这样,boost::call_traits::param_type 将是正确的选择。

问题:标准中是否有任何保证?

(我意识到这个问题实际上与基于范围的 for 循环无关。使用 const_iterators 时也存在。)

【问题讨论】:

    标签: c++ templates c++11


    【解决方案1】:

    标准容器都从它们的迭代器返回引用(但是请注意,一些“容器不是真正的容器,例如,std::vector&lt;bool&gt; 返回一个代理)。其他迭代器可能返回代理或值,尽管这不是严格支持。

    当然,该标准不对性能做出任何保证。任何与性能相关的特性(除了复杂性保证之外)都被视为实现质量。

    也就是说,您可能需要考虑让编译器像以前一样为您做出选择:

    for (auto&& e: coll) { f(e); }
    

    这里的主要问题是f() 可能会收到非const 引用。如有必要,可以使用collconst 版本来防止这种情况发生。

    【讨论】:

    • 或者...只使用auto const&amp;。 ;)
    • @Xeo:如果迭代器产生一个值而不是对值的 [const] 引用,则表示法 T&amp;&amp; 推导出值而不是对值的引用。
    • 嗯? auto&amp;&amp; 将始终是一个引用,无论迭代器产生什么。如果它确实产生了一个值,那么它将只是一个右值引用。 (注意:我可能误解了你的评论。)
    • @Xeo:嗯,对,e 的类型不是值类型,而是T&amp;&amp; 的某些类型T。当使用auto const&amp; e 时,e 的类型是T const&amp;。要真正利用差异,可能需要使用f(std::forward&lt;decltype(e)&gt;(e)),这不是很漂亮。
    • @Daniel: auto const&amp;&amp; 不会是通用引用,只会绑定到右值。
    【解决方案2】:

    6.5.4/1 说:

    for ( for-range-declaration : braced-init-list ) statement
    

    让 range-init 等价于花括号初始化列表。在每种情况下,一个 基于范围的 for 语句等价于

    {
        auto && __range = range-init;
        for ( auto __begin = begin-expr,
                    __end = end-expr;
                __begin != __end;
                ++__begin ) {
            for-range-declaration = *__begin;
            statement
        }
    }
    

    (进一步解释所有__gubbins的含义)。

    标准没有做出任何保证该行const auto &amp;e = *__begin是否会引入性能开销,当然,与在语句*__begin而不是e相比/em>。允许实现通过费力地将指针复制到某个堆栈槽中然后在每次使用引用时将其读回来实现引用,并且不需要优化。

    但是没有理由在一个明智的编译器中应该有一个开销,在__begin是一个容器迭代器(它的operator*返回一个引用)的情况下,然后e在声明。

    【讨论】:

    • 谢谢史蒂夫。那么如何编写一个通用的基于范围的 for 循环呢?
    • @Daniel:我认为for (const auto&amp; e : coll) { foo_func(e); } 很好,但我没有使用足够的 C++11 来声称能够为自己编写一个风格指南来说明它。 boost::call_traits::param_type 的用途是,一旦你传递了一个参数,那么在使用引用而不是小对象类型时会有潜在的开销,因为有一个调用约定会限制事物。在一个函数中,我认为我相信编译器会将引用变量视为“纯”别名。如果*__begin 按值返回,我不会赌在不检查的情况下避免额外的临时性。
    猜你喜欢
    • 1970-01-01
    • 2013-05-27
    • 2013-12-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-27
    • 1970-01-01
    相关资源
    最近更新 更多