迭代基本类型时使用const引用的任何缺点?

时间:2012-10-24 20:55:50

标签: c++ templates c++11

我发现自己最近越来越多地使用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时也会出现这种情况。)

2 个答案:

答案 0 :(得分:13)

标准容器都返回来自迭代器的引用(但是,请注意,某些“容器实际上不是容器,例如,std::vector<bool>返回代理”。其他迭代器可能会返回代理或值,尽管这不是严格支持。

当然,该标准对性能没有任何保证。任何与性能相关的特性(超出复杂性保证)都被认为是实施的质量。

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

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

这里的主要问题是f()可能会收到非const引用。如果需要,可以使用const版本coll来阻止这种情况。

答案 1 :(得分:11)

6.5.4 / 1说:

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

让range-init等同于braced-init-list。在每种情况下,a   基于范围的语句等同于

{
    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而不是*__begin相比,该标准不会产生任何保证该行e是否会带来性能开销在语句中。允许实现通过费力地将指针复制到某个堆栈槽中来实现引用,然后在每次使用引用时将其读回,并且不需要进行优化。

但是,在__begin是容器迭代器(operator*返回引用)的情况下,没有理由在合理的编译器中存在开销,然后e语句中按值传递。