C++イテレータとパフォーマンス**に関するスタックオーバーフローのさまざまな質問をここで読んだところ、for(auto& elem : container)
がコンパイラによって最適なバージョンに「拡張」されるのかどうか疑問に思い始めましたか? (auto
のようなもので、コンパイラはすぐに正しい型を推測するため、遅くなることはなく、時には速くなることもあります)。
**例えば、あなたが書くかどうかは重要ですか
for(iterator it = container.begin(), eit = container.end(); it != eit; ++it)
または
for(iterator it = container.begin(); it != container.end(); ++it)
無効化しないコンテナの場合?
標準はあなたの友達です、[stmt.ranged]/1を参照してください
フォームの範囲ベースのforステートメントの場合
for ( for-range-declaration : expression ) statement
range-initを括弧で囲まれた式と同等にする
( expression )
フォームの範囲ベースのforステートメント
for ( for-range-declaration : braced-init-list ) statement
range-initをbraced-init-listと同等にします。いずれの場合も、範囲ベースの
for
ステートメントは{ auto && __range = range-init; for ( auto __begin = begin-expr, __end = end-expr; __begin != __end; ++__begin ) { for-range-declaration = *__begin; statement } }
したがって、はい、標準は可能な限り最良の形式が達成されることを保証します。
また、vector
などの多くのコンテナでは、この反復中にコンテナを変更(挿入/消去)することは未定義の動作です。
Range-forは、エンドイテレータをキャッシュするため、可能な限り高速です[ 提供された引用 ]、プリインクリメントを使用し、イテレータを一度だけ逆参照します。
だからあなたが書く傾向があるなら:
for(iterator i = cont.begin(); i != cont.end(); i++) { /**/ }
次に、そうです、range-forはわずかに高速になる可能性があります。これは、適切な場合にそれを使用しない理由がないため、記述も簡単だからです。
N.B。私はそれが可能な限り高速であると述べましたが、それは可能な限り高速ではありません。手動ループを注意深く作成すると、まったく同じパフォーマンスを実現できます。
好奇心から、私は両方のアプローチのアセンブリコードを調べることにしました。
int foo1(const std::vector<int>& v) {
int res = 0;
for (auto x : v)
res += x;
return res;
}
int foo2(const std::vector<int>& v) {
int res = 0;
for (std::vector<int>::const_iterator it = v.begin(); it != v.end(); ++it)
res += *it;
return res;
}
また、アセンブリコード(-O3とgcc 4.6を使用)は両方のアプローチでまったく同じです(foo2
のコードは完全に同じであるため省略されています)。
080486d4 <foo1(std::vector<int, std::allocator<int> > const&)>:
80486d4: 8b 44 24 04 mov 0x4(%esp),%eax
80486d8: 8b 10 mov (%eax),%edx
80486da: 8b 48 04 mov 0x4(%eax),%ecx
80486dd: b8 00 00 00 00 mov $0x0,%eax
80486e2: 39 ca cmp %ecx,%edx
80486e4: 74 09 je 80486ef <foo1(std::vector<int, std::allocator<int> > const&)+0x1b>
80486e6: 03 02 add (%edx),%eax
80486e8: 83 c2 04 add $0x4,%edx
80486eb: 39 d1 cmp %edx,%ecx
80486ed: 75 f7 jne 80486e6 <foo1(std::vector<int, std::allocator<int> > const&)+0x12>
80486ef: f3 c3 repz ret
したがって、はい、両方のアプローチは同じです。
[〜#〜] update [〜#〜]:vector<string>
やmap<string, string>
などの他のコンテナー(または要素タイプ)でも同じことが言えます。そのような場合、範囲ベースのループで参照を使用することが特に重要です。それ以外の場合は、一時ファイルが作成され、多くの追加コードが表示されます(前の例では、vector
にはint
値のみが含まれていたため、必要ありませんでした)。
map<string, string>
の場合、使用されるC++コードスニペットは次のとおりです。
int foo1(const std::map<std::string, std::string>& v) {
int res = 0;
for (const auto& x : v) {
res += (x.first.size() + x.second.size());
}
return res;
}
int foo2(const std::map<std::string, std::string>& v) {
int res = 0;
for (auto it = v.begin(), end = v.end(); it != end; ++it) {
res += (it->first.size() + it->second.size());
}
return res;
}
また、アセンブリコード(両方の場合)は次のとおりです。
8048d70: 56 Push %esi
8048d71: 53 Push %ebx
8048d72: 31 db xor %ebx,%ebx
8048d74: 83 ec 14 sub $0x14,%esp
8048d77: 8b 74 24 20 mov 0x20(%esp),%esi
8048d7b: 8b 46 0c mov 0xc(%esi),%eax
8048d7e: 83 c6 04 add $0x4,%esi
8048d81: 39 f0 cmp %esi,%eax
8048d83: 74 1b je 8048da0
8048d85: 8d 76 00 lea 0x0(%esi),%esi
8048d88: 8b 50 10 mov 0x10(%eax),%edx
8048d8b: 03 5a f4 add -0xc(%edx),%ebx
8048d8e: 8b 50 14 mov 0x14(%eax),%edx
8048d91: 03 5a f4 add -0xc(%edx),%ebx
8048d94: 89 04 24 mov %eax,(%esp)
8048d97: e8 f4 fb ff ff call 8048990 <std::_Rb_tree_increment(std::_Rb_tree_node_base const*)@plt>
8048d9c: 39 c6 cmp %eax,%esi
8048d9e: 75 e8 jne 8048d88
8048da0: 83 c4 14 add $0x14,%esp
8048da3: 89 d8 mov %ebx,%eax
8048da5: 5b pop %ebx
8048da6: 5e pop %esi
8048da7: c3 ret
まれに、より高速になる可能性があります。イテレータに名前を付けることができないため、オプティマイザはループがイテレータを変更できないことをより簡単に証明できます。これは、例えばループ展開の最適化。
いいえ。イテレータを使用した古いfor
ループと同じです。結局のところ、範囲ベースのfor
はイテレータと内部的に連携します。コンパイラは、両方に対して同等のコードを生成するだけです。