std::for_each
for
ループに勝る利点はありますか?私にとって、std::for_each
はコードの可読性を妨げるだけのようです。なぜいくつかのコーディング標準がその使用を推奨するのですか?
C++ 11 (以前はC++ 0xと呼ばれていました)の良いところは、この面倒な議論が解決することです。
つまり、コレクション全体を反復処理することを望んでいる彼らの正しい心の誰も、まだこれを使用しません
for(auto it = collection.begin(); it != collection.end() ; ++it)
{
foo(*it);
}
またはこれ
for_each(collection.begin(), collection.end(), [](Element& e)
{
foo(e);
});
range-based for
loop構文が利用可能な場合:
for(Element& e : collection)
{
foo(e);
}
この種の構文はJavaとC#でしばらく使用可能になっており、実際、最近見たすべてのJavaまたはC#コードには、従来のforeach
ループよりもはるかに多くのfor
ループがあります。
いくつかの理由があります。
あなたがそれに慣れていない、そして/またはそれを本当に簡単にするために適切なツールを使用していないという理由だけで、読みやすさを妨げるようです。 (ヘルパーについては、boost :: rangeおよびboost :: bind/boost :: lambdaを参照してください。これらの多くはC++ 0xに移行し、for_eachおよび関連関数をより便利にします。)
任意のイテレーターで機能するfor_eachの上にアルゴリズムを書くことができます。
それは愚かなタイピングバグの可能性を減らします。
また、find_if
、sort
、replace
など、STLアルゴリズムの残りの部分にも心を開き、これらはもはや奇妙に見えなくなります。これは大きな勝利になる可能性があります。
更新1:
最も重要なのは、for_each
対for-loopsを超えて、find/sort/partition/copy_replace_if、parallell execution ..などのような他のSTL-alogを調べるのに役立ちます。
For_eachの兄弟の「残り」を使用して、多くの処理を非常に簡潔に記述できますが、さまざまな内部ロジックを使用してforループを記述するだけであれば、それらの使用方法を学ぶことはできません。何度も何度も車輪を発明することになります。
そして(近日公開予定の範囲スタイルfor_each):
for_each(monsters, boost::mem_fn(&Monster::think));
または、C++ x11ラムダの場合:
for_each(monsters, [](Monster& m) { m.think(); });
iMOは以下よりも読みやすい
for(Monsters::iterator i = monsters.begin(); i != monsters.end(); ++i) {
i->think();
}
また、これ(またはラムダ付き、他を参照):
for_each(bananas, boost::bind(&Monkey::eat, my_monkey, _1));
より簡潔です:
for(Bananas::iterator i = bananas.begin(); i != bananas.end(); ++i) {
my_monkey->eat(*i);
}
特に、順番に呼び出す関数がいくつかある場合は...でも、それは私だけかもしれません。 ;)
更新2:反復子のペアの代わりに範囲で動作するstl-algosの独自のワンライナーラッパーを作成しました。 boost :: range_exがリリースされると、それが含まれ、C++ 0xにも含まれるでしょうか?
for_each
はより一般的です。これを使用して、(開始/終了イテレータを渡すことにより)あらゆるタイプのコンテナを反復処理できます。繰り返しコードを更新することなく、for_each
を使用する関数の下のコンテナを潜在的にスワップアウトできます。 std::vector
の利点を確認するには、世界にfor_each
およびプレーンな古いC配列以外のコンテナがあることを考慮する必要があります。
for_each
の主な欠点は、ファンクターを必要とするため、構文がぎこちないことです。これは、ラムダの導入によりC++ 0xで修正されました。
std::vector<int> container;
...
std::for_each(container.begin(), container.end(), [](int& i){
i+= 10;
});
これは3年後には奇妙に見えません。
個人的に、std::for_each
(特別な目的のファンクター/複雑なboost::lambda
sを書く)を使用するために邪魔をする必要があるときはいつでも、BOOST_FOREACH
とC++ 0xの範囲を見つけます。より明確に基づく:
BOOST_FOREACH(Monster* m, monsters) {
if (m->has_plan())
m->act();
}
対
std::for_each(monsters.begin(), monsters.end(),
if_then(bind(&Monster::has_plan, _1),
bind(&Monster::act, _1)));
その非常に主観的な、一部の人は、for_each
を使用するとコードがmore読み取り可能になると言うでしょう。これは、同じ規則で異なるコレクションを扱うことができるからです。 for_each
itslefはループとして実装されます
template<class InputIterator, class Function>
Function for_each(InputIterator first, InputIterator last, Function f)
{
for ( ; first!=last; ++first ) f(*first);
return f;
}
自分に合ったものを選ぶのはあなた次第です。
あなたはほとんど正しいです。ほとんどの場合、std::for_each
は純損失です。 for_each
とgoto
を比較するところまで行きます。 goto
は、可能な限り最も汎用的なフロー制御を提供します。これを使用して、想像できる他の制御構造を実質的に実装できます。ただし、非常に汎用性が高いということは、goto
を単独で見ることで、この状況で何をするつもりなのかを事実上nothingで知ることができるということです。その結果、最後の手段として以外、goto
を使用する人はほとんどいません。
標準アルゴリズムの中で、for_each
はほぼ同じ方法です-ほぼすべてを実装するために使用できます。つまり、for_each
を見ると、この状況で何に使用されているかについてほとんど何もわかりません。残念ながら、for_each
に対する人々の態度は、goto
に対する彼らの態度が(たとえば)1970年頃であった場所についてです-少数最後の手段としてのみ使用されますが、多くは依然としてそれをプライマリアルゴリズムと見なしており、他のアルゴリズムを使用することはほとんどありません。ほとんどの場合、一目見ただけでも、選択肢の1つが劇的に優れていることがわかります。
たとえば、for_each
を使用してコレクションの内容を印刷するコードを書いている人を見たことが何度かあるのを忘れてしまったと確信しています。私が見た投稿に基づいて、これはおそらくfor_each
の最も一般的な使用法の1つかもしれません。それらは次のようなものになります:
class XXX {
// ...
public:
std::ostream &print(std::ostream &os) { return os << "my data\n"; }
};
そして、彼らの投稿は、bind1st
、mem_fun
などのどの組み合わせについて、次のようなものを作成する必要があるかを尋ねています。
std::vector<XXX> coll;
std::for_each(coll.begin(), coll.end(), XXX::print);
動作し、coll
の要素を出力します。私がそこに書いたとおりに実際に動作した場合、それは平凡ですが、動作しません-そして、あなたが動作するようになった時までに、何に関連するいくつかのコードを見つけることは困難ですそれを一緒に保持する部分の間で起こっています。
幸いなことに、もっと良い方法があります。 XXXの通常のストリーム挿入オーバーロードを追加します。
std::ostream &operator<<(std::ostream *os, XXX const &x) {
return x.print(os);
}
std::copy
を使用します:
std::copy(coll.begin(), coll.end(), std::ostream_iterator<XXX>(std::cout, "\n"));
それは機能します-coll
の内容をstd::cout
に出力することを理解するために、実質的にまったく作業を必要としません。
多くのアルゴリズム関数と同様に、最初の反応は、ループよりもforeachを使用する方が読みにくいと考えることです。それは多くの火炎戦争のトピックでした。
イディオムに慣れたら、それが便利だと思うかもしれません。明らかな利点の1つは、実際の反復機能からループの内部コンテンツをコーダーに強制的に分離させることです。 (OK、私はそれが利点だと思います。他の人はあなたが本当の利益なしでコードを切り刻んでいると言います)。
もう1つの利点は、foreachを見ると、 知っている すべてのアイテムが処理されるか、例外がスローされます。
A for loopには、ループを終了するためのいくつかのオプションがあります。ループに完全なコースを実行させるか、または ブレーク ループから明示的に飛び出すキーワード、または 帰る 関数全体のループを終了するキーワード。対照的に、 foreach これらのオプションは許可されず、これにより読みやすくなります。関数名を見るだけで、反復の完全な性質を知ることができます。
紛らわしい例です for ループ:
for(std::vector<widget>::iterator i = v.begin(); i != v.end(); ++i)
{
/////////////////////////////////////////////////////////////////////
// Imagine a page of code here by programmers who don't refactor
///////////////////////////////////////////////////////////////////////
if(widget->Cost < calculatedAmountSofar)
{
break;
}
////////////////////////////////////////////////////////////////////////
// And then some more code added by a stressed out juniour developer
// *#&$*)#$&#(#)$#(*$&#(&*^$#(*$#)($*#(&$^#($*&#)$(#&*$&#*$#*)$(#*
/////////////////////////////////////////////////////////////////////////
for(std::vector<widgetPart>::iterator ip = widget.GetParts().begin(); ip != widget.GetParts().end(); ++ip)
{
if(ip->IsBroken())
{
return false;
}
}
}
より読みやすくするために関数を書くことの利点は、for(...)
およびfor_each(...
)のときに現れないかもしれません。
Forループを使用する代わりに、functional.hのすべてのアルゴリズムを使用すると、コードがはるかに読みやすくなります。
iterator longest_tree = std::max_element(forest.begin(), forest.end(), ...);
iterator first_leaf_tree = std::find_if(forest.begin(), forest.end(), ...);
std::transform(forest.begin(), forest.end(), firewood.begin(), ...);
std::for_each(forest.begin(), forest.end(), make_plywood);
muchより読みやすい;
Forest::iterator longest_tree = it.begin();
for (Forest::const_iterator it = forest.begin(); it != forest.end(); ++it{
if (*it > *longest_tree) {
longest_tree = it;
}
}
Forest::iterator leaf_tree = it.begin();
for (Forest::const_iterator it = forest.begin(); it != forest.end(); ++it{
if (it->type() == LEAF_TREE) {
leaf_tree = it;
break;
}
}
for (Forest::const_iterator it = forest.begin(), jt = firewood.begin();
it != forest.end();
it++, jt++) {
*jt = boost::transformtowood(*it);
}
for (Forest::const_iterator it = forest.begin(); it != forest.end(); ++it{
std::makeplywood(*it);
}
そして、それは私がとても素敵だと思うことです、forループを1行の関数に一般化します=)
簡単:for_each
は、すべての配列項目を処理する関数が既にある場合に便利です。したがって、ラムダを記述する必要はありません。確かに、これ
for_each(a.begin(), a.end(), a_item_handler);
よりも良い
for(auto& item: a) {
a_item_handler(a);
}
また、範囲のfor
ループは、コンテナ全体を最初から最後まで繰り返しますが、for_each
はより柔軟です。
for_each
ループは、イテレーター(ループの実装方法の詳細)をユーザーコードから隠し、操作の明確なセマンティクスを定義することを目的としています。各要素は1回だけ反復されます。
現在の標準の可読性の問題は、コードのブロックの代わりに最後の引数としてファンクターを必要とすることです。そのため、多くの場合、特定のファンクタータイプを記述する必要があります。ファンクタオブジェクトをインプレースで定義できないため(関数内で定義されたローカルクラスをテンプレート引数として使用できない)、ループの実装を実際のループから移動する必要があるため、コードは読みにくくなります。
struct myfunctor {
void operator()( int arg1 ) { code }
};
void apply( std::vector<int> const & v ) {
// code
std::for_each( v.begin(), v.end(), myfunctor() );
// more code
}
各オブジェクトで特定の操作を実行する場合は、std::mem_fn
、またはboost::bind
(次の標準ではstd::bind
)、またはboost::lambda
(ラムダは次の標準)それをより簡単にする:
void function( int value );
void apply( std::vector<X> const & v ) {
// code
std::for_each( v.begin(), v.end(), boost::bind( function, _1 ) );
// code
}
あなたが代わりに呼び出す関数/メソッドを持っている場合、これは手巻きバージョンよりも読みにくく、コンパクトではありません。実装は、for_each
ループの他の実装を提供できます(並列処理を考えてください)。
今後の標準では、いくつかの欠点をさまざまな方法で処理します。テンプレートへの引数としてローカルに定義されたクラスを許可します。
void apply( std::vector<int> const & v ) {
// code
struct myfunctor {
void operator()( int ) { code }
};
std::for_each( v.begin(), v.end(), myfunctor() );
// code
}
コードのローカリティの改善:閲覧すると、コードの実行内容がすぐにわかります。実際のところ、ファンクタを定義するためにクラス構文を使用する必要はありませんが、ラムダを使用します:
void apply( std::vector<int> const & v ) {
// code
std::for_each( v.begin(), v.end(),
[]( int ) { // code } );
// code
}
for_each
の場合でも、それをより自然にする特定の構造があります。
void apply( std::vector<int> const & v ) {
// code
for ( int i : v ) {
// code
}
// code
}
私はfor_each
コンストラクトと手巻きループを混ぜる傾向があります。既存の関数またはメソッドへの呼び出しだけが必要な場合(for_each( v.begin(), v.end(), boost::bind( &Type::update, _1 ) )
)、コードから多くのボイラープレートイテレーターのものを取り除くfor_each
コンストラクトを探します。もっと複雑なものが必要で、実際の使用より2、3行上にファンクターを実装できない場合は、独自のループを作成します(操作を適切に維持します)。コードの重要でないセクションでは、BOOST_FOREACHを使用する場合があります(同僚が私に夢中にさせた)
std::for_each
が嫌いだったので、ラムダなしではまったく間違っていたと思っていました。しかし、私はしばらく前に気が変わったので、今では実際にそれを愛しています。そして、読みやすさも向上し、TDDの方法でコードをテストしやすくなると思います。
std::for_each
アルゴリズムは、範囲内のすべての要素で何かを行うとして読み取ることができます。これにより、canが読みやすくなります。実行するアクションは20行の長さで、アクションが実行される機能も約20行の長さであるとします。これにより、関数は従来のforループでは40行、std::for_each
では約20行になり、理解しやすくなります。
std::for_each
のファンクターは、より一般的であり、したがって再利用可能です。
struct DeleteElement
{
template <typename T>
void operator()(const T *ptr)
{
delete ptr;
}
};
また、コードには、std::for_each(v.begin(), v.end(), DeleteElement())
のようなワンライナーがあります。これは、明示的なループよりもわずかに優れたIMOです。
これらのファンクターはすべて、通常、長い関数の途中で明示的なforループを実行するよりも単体テストで取得する方が簡単であり、それだけでもすでに私にとって大きな勝利です。
std::for_each
はまた、一般的に信頼性が高くなります。これは、範囲を間違える可能性が低いためです。
最後に、コンパイラは、特定の種類の手作りのforループよりも、[for_each)alwaysよりもstd::for_each
に対してわずかに優れたコードを生成する場合がありますコンパイラにとっても同じように見えます。コンパイラの作成者は、知識を最大限に活用して、できる限り良いものにすることができます。
find_if
、transform
などの他のstdアルゴリズムにも同じことが当てはまります。
読みやすさとパフォーマンスは別として、見落とされがちな側面の1つは一貫性です。イテレータに対するfor(またはwhile)ループを実装するには、次の多くの方法があります。
for (C::iterator iter = c.begin(); iter != c.end(); iter++) {
do_something(*iter);
}
に:
C::iterator iter = c.begin();
C::iterator end = c.end();
while (iter != end) {
do_something(*iter);
++iter;
}
さまざまなレベルの効率とバグの可能性の間に多くの例があります。
ただし、for_eachを使用すると、ループを抽象化することで一貫性が強化されます。
for_each(c.begin(), c.end(), do_something);
今心配する必要があるのは、BoostまたはC++ 0x機能を使用して、関数、ファンクター、またはラムダとしてループ本体を実装しますか?個人的には、ランダムなfor/whileループを実装または読み取る方法よりも、それを心配します。
STLの他のアルゴリズムを頻繁に使用する場合、for_each
にはいくつかの利点があります。
従来のforループとは異なり、for_each
を使用すると、任意の入力反復子で機能するコードを強制的に記述できます。この方法で制限されることは、実際には良いことです。
for_each
を使用するように求められることなく、これを行うことはできません。for_each
を使用すると、より具体的なSTL関数を使用して同じことを実行できることが明らかになる場合があります。 (Jerry Coffinの例のように、for_each
が最良のオプションであるとは限りませんが、forループが唯一の選択肢ではありません。)
C++ 11と2つの単純なテンプレートを使用すると、次のように記述できます。
for ( auto x: range(v1+4,v1+6) ) {
x*=2;
cout<< x <<' ';
}
for_each
またはループの代わりとして。なぜそれを選択するのが簡潔で安全であるかを要約すると、そこにない表現に間違いの可能性はありません。
私にとって、for_each
は、ループ本体がすでにファンクターである場合、同じ理由で常に優れていたため、可能な限り活用します。
まだ3つの式for
を使用していますが、今では理解できるものがあることがわかっている場合、それは定型ではありません。 I hate定型句。私はその存在にresしています。それは実際のコードではなく、読むことで学ぶことは何もありません。チェックが必要なもう1つのことです。精神的な努力は、それをチェックするのがどれだけ簡単であるかによって測定することができます。
テンプレートは
template<typename iter>
struct range_ {
iter begin() {return __beg;} iter end(){return __end;}
range_(iter const&beg,iter const&end) : __beg(beg),__end(end) {}
iter __beg, __end;
};
template<typename iter>
range_<iter> range(iter const &begin, iter const &end)
{ return range_<iter>(begin,end); }
for
は、各要素または3分の1などを繰り返すことができるforループです。for_each
は、各要素のみを繰り返すためのものです。名前から明らかです。したがって、コードで何をしようとしているのかがより明確になります。
ほとんどの場合 コレクション全体を反復処理します。したがって、2つのパラメーターのみを使用して、独自のfor_each()バリアントを作成することをお勧めします。これにより、 Terry Mahaffeyの例 を次のように書き換えることができます。
for_each(container, [](int& i) {
i += 10;
});
これはforループよりも実際に読みやすいと思います。ただし、これにはC++ 0xコンパイラ拡張機能が必要です。
For_eachは可読性が悪いと感じています。コンセプトは良いものですが、c ++を使用すると、少なくとも私にとっては、読みやすく書くことが非常に難しくなります。 c ++ 0xラムダ式が役立ちます。私はラムダのアイデアが本当に好きです。しかし、一見したところ、構文は非常にいものであり、これに慣れることに100%確信はありません。たぶん5年後に私はそれに慣れてしまい、考え直しませんが、多分そうではないでしょう。時が教えてくれる :)
使用したい
vector<thing>::iterator istart = container.begin();
vector<thing>::iterator iend = container.end();
for(vector<thing>::iterator i = istart; i != iend; ++i) {
// Do stuff
}
明示的なforループの方が読みやすく、開始イテレーターと終了イテレーターに名前付き変数を使用すると明示的にforループの混乱が減ります。
もちろん、場合によって異なりますが、これは私が通常最も見つけやすいものです。
for_each
を使用して、 Fork-Joinパターン を実装できます。それ以外は fluent-interface をサポートしています。
複数のワーカーでラムダタスクを呼び出すことで、異種並列コンピューティングにcuda/gpuを使用する実装gpu::for_each
を追加できます。
gpu::for_each(users.begin(),users.end(),update_summary);
// all summary is complete now
// go access the user-summary here.
また、gpu::for_each
は、次のステートメントを実行する前に、ワーカーがすべてのラムダタスクで作業を完了するのを待つ場合があります。
これにより、人間が読み取れるコードを簡潔に書くことができます。
accounts::erase(std::remove_if(accounts.begin(),accounts.end(),used_this_year));
std::for_each(accounts.begin(),accounts.end(),mark_dormant);
Forループが壊れることがあります。私はハーブ・サッターのオウムになりたくないので、ここに彼のプレゼンテーションへのリンクがあります: http://channel9.msdn.com/Events/BUILD/BUILD2011/TOOL-835T 必ず読んでくださいコメントも:)
反復子は、ループの各反復で実行される関数の呼び出しにすることができます。
こちらをご覧ください: http://www.cplusplus.com/reference/algorithm/for_each/