web-dev-qa-db-ja.com

forループに対するstd :: for_eachの利点

std::for_eachforループに勝る利点はありますか?私にとって、std::for_eachはコードの可読性を妨げるだけのようです。なぜいくつかのコーディング標準がその使用を推奨するのですか?

158
missingfaktor

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ループがあります。

167
Thomas Petit

いくつかの理由があります。

  1. あなたがそれに慣れていない、そして/またはそれを本当に簡単にするために適切なツールを使用していないという理由だけで、読みやすさを妨げるようです。 (ヘルパーについては、boost :: rangeおよびboost :: bind/boost :: lambdaを参照してください。これらの多くはC++ 0xに移行し、for_eachおよび関連関数をより便利にします。)

  2. 任意のイテレーターで機能するfor_eachの上にアルゴリズムを書くことができます。

  3. それは愚かなタイピングバグの可能性を減らします。

  4. また、find_ifsortreplaceなど、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にも含まれるでしょうか?

48
Macke

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年後には奇妙に見えません。

22
Terry Mahaffey

個人的に、std::for_each(特別な目的のファンクター/複雑なboost::lambdasを書く)を使用するために邪魔をする必要があるときはいつでも、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)));
17
UncleBens

その非常に主観的な、一部の人は、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;
  }

自分に合ったものを選ぶのはあなた次第です。

11
Alon

あなたはほとんど正しいです。ほとんどの場合、std::for_eachは純損失です。 for_eachgotoを比較するところまで行きます。 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"; }
};

そして、彼らの投稿は、bind1stmem_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に出力することを理解するために、実質的にまったく作業を必要としません。

10
Jerry Coffin

多くのアルゴリズム関数と同様に、最初の反応は、ループよりもforeachを使用する方が読みにくいと考えることです。それは多くの火炎戦争のトピックでした。

イディオムに慣れたら、それが便利だと思うかもしれません。明らかな利点の1つは、実際の反復機能からループの内部コンテンツをコーダーに強制的に分離させることです。 (OK、私はそれが利点だと思います。他の人はあなたが本当の利益なしでコードを切り刻んでいると言います)。

もう1つの利点は、foreachを見ると、 知っている すべてのアイテムが処理されるか、例外がスローされます。

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;
      }
   }
}
10
Andrew Shepherd

より読みやすくするために関数を書くことの利点は、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行の関数に一般化します=)

8
Viktor Sehr

簡単:for_eachは、すべての配列項目を処理する関数が既にある場合に便利です。したがって、ラムダを記述する必要はありません。確かに、これ

for_each(a.begin(), a.end(), a_item_handler);

よりも良い

for(auto& item: a) {
    a_item_handler(a);
}

また、範囲のforループは、コンテナ全体を最初から最後まで繰り返しますが、for_eachはより柔軟です。

6
Tigran Saluev

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_iftransformなどの他のstdアルゴリズムにも同じことが当てはまります。

3
Dmitry

読みやすさとパフォーマンスは別として、見落とされがちな側面の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ループを実装または読み取る方法よりも、それを心配します。

3
Jason Govig

STLの他のアルゴリズムを頻繁に使用する場合、for_eachにはいくつかの利点があります。

  1. 多くの場合、forループよりも簡単でエラーが少なくなります。これは、このインターフェイスを使用する関数に慣れているためと、多くの場合、実際にはもう少し簡潔であるためです。
  2. 範囲ベースのforループはさらにシンプルになりますが、柔軟性が低くなります(Adrian McCarthyが述べているように、コンテナー全体を反復処理します)。
  3. 従来のforループとは異なり、for_eachを使用すると、任意の入力反復子で機能するコードを強制的に記述できます。この方法で制限されることは、実際には良いことです。

    1. 実際には、後で別のコンテナで機能するようにコードを調整する必要がある場合があります。
    2. 最初は、何かを教えたり、習慣を改善したりします。
    3. 常に完全に同等のforループを作成する場合でも、同じコードを変更する他の人は、for_eachを使用するように求められることなく、これを行うことはできません。
  4. for_eachを使用すると、より具体的なSTL関数を使用して同じことを実行できることが明らかになる場合があります。 (Jerry Coffinの例のように、for_eachが最良のオプションであるとは限りませんが、forループが唯一の選択肢ではありません。)

2

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); }
2
jthill

forは、各要素または3分の1などを繰り返すことができるforループです。for_eachは、各要素のみを繰り返すためのものです。名前から明らかです。したがって、コードで何をしようとしているのかがより明確になります。

ほとんどの場合 コレクション全体を反復処理します。したがって、2つのパラメーターのみを使用して、独自のfor_each()バリアントを作成することをお勧めします。これにより、 Terry Mahaffeyの例 を次のように書き換えることができます。

for_each(container, [](int& i) {
    i += 10;
});

これはforループよりも実際に読みやすいと思います。ただし、これにはC++ 0xコンパイラ拡張機能が必要です。

1
Dimitri C.

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ループの混乱が減ります。

もちろん、場合によって異なりますが、これは私が通常最も見つけやすいものです。

1
jcoder

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);
0
shuva

Forループが壊れることがあります。私はハーブ・サッターのオウムになりたくないので、ここに彼のプレゼンテーションへのリンクがあります: http://channel9.msdn.com/Events/BUILD/BUILD2011/TOOL-835T 必ず読んでくださいコメントも:)

0
NoSenseEtAl

反復子は、ループの各反復で実行される関数の呼び出しにすることができます。

こちらをご覧ください: http://www.cplusplus.com/reference/algorithm/for_each/

0
sdornan