35

我发现自己最近越来越多地使用 C++11,过去我会使用迭代器,现在我尽可能使用基于范围的 for 循环:

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 时也存在。)

4

2 回答 2

15

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

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

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

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

这里的主要问题是f()可能会收到非const参考。如有必要,可以const使用coll.

于 2012-10-24T21:13:53.203 回答
11

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 &e = *__begin*__begine

But there's no reason why there should be an overhead in a sensible compiler, in the case where __begin is a container iterator (whose operator* returns a reference), and then e is passed by value in statement.

于 2012-10-24T21:16:40.287 回答