我发现自己最近越来越多地使用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时也会出现这种情况。)
答案 0 :(得分:13)
标准容器都返回来自迭代器的引用(但是,请注意,某些“容器实际上不是容器,例如,std::vector<bool>
返回代理”。其他迭代器可能会返回代理或值,尽管这不是严格支持。
当然,该标准对性能没有任何保证。任何与性能相关的特性(超出复杂性保证)都被认为是实施的质量。
那就是说,您可能需要考虑让编译器像以前一样为您做出选择:
for (auto&& e: coll) { f(e); }
这里的主要问题是f()
可能会收到非const
引用。如果需要,可以使用const
版本coll
来阻止这种情况。
答案 1 :(得分:11)
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
在语句中按值传递。