std::for_each
オーバーfor
ループの利点はありますか?私には、std::for_each
コードの可読性を妨げるだけのようです。では、なぜいくつかのコーディング標準がその使用を推奨しているのですか?
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);
});
とき範囲ベースのfor
ループ構文を使用できます。
for(Element& e : collection)
{
foo(e);
}
この種の構文は、しばらくの間JavaとC#で利用可能であり、実際、最近のすべてのJavaまたはC#コードにはforeach
、従来のfor
ループよりもはるかに多くのループがあります。
Element & e
と auto & e
(あるいはauto const &e
良く見えます)。Element const e
暗黙的な変換が必要な場合、たとえばソースがさまざまなタイプのコレクションであり、それらをに変換したい場合は(参照なしで)使用しElement
ます。
ここにいくつかの理由があります:
慣れていなかったり、周りに適切なツールを使用していないために、読みやすさが妨げられているようです。(ヘルパーについては、boost :: rangeおよびboost :: bind / boost :: lambdaを参照してください。これらの多くはC ++ 0xに入り、for_eachおよび関連する関数がより便利になります。)
これにより、任意のイテレーターで機能するアルゴリズムをfor_eachの上に記述できます。
それは愚かなタイピングのバグの可能性を減らします。
それはまたのような、STL-アルゴリズムの残りの部分にあなたの心を開きfind_if
、sort
、replace
など、これらはもうそれほど奇妙に見えることはありません。これは大きな勝利になる可能性があります。
更新1:
最も重要なのは、それだけでなくfor_each
forループを超えて、find / sort / partition / copy_replace_if、並列実行などの他のSTLアナログを確認するのに役立ちます。
多くの処理は、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の独自の1行ラッパーを作成しました。boost :: range_ex、一度リリースされると、それが含まれ、おそらくC ++ 0xにも存在するでしょうか?
outer_class::inner_class::iterator
または彼らはテンプレート引数です:typename std::vector<T>::iterator
...自体は、それ自体に多くのライン構築物に実行することができます構築物について
for_each
第二の例では、正しくない(する必要がありますfor_each( bananas.begin(), bananas.end(),...
for_each
より一般的です。これを使用して、任意のタイプのコンテナーを反復できます(開始/終了反復子を渡すことにより)。潜在的にfor_each
、反復コードを更新せずにを使用する関数の下のコンテナーを交換できます。std::vector
の利点を確認するには、世界中にプレーンな古いC配列以外のコンテナがあることを考慮する必要がありますfor_each
。
の主な欠点for_each
は、ファンクタが必要になるため、構文が不格好です。これはラムダの導入によりC ++ 11(以前のC ++ 0x)で修正されています。
std::vector<int> container;
...
std::for_each(container.begin(), container.end(), [](int& i){
i+= 10;
});
これは3年間であなたにとって奇妙に見えなくなります。
for ( int v : int_vector ) {
(それはBOOST_FOREACHで今日シミュレートすることができたとしても)
std::for_each(container, [](int& i){ ... });
。つまり、なぜコンテナを2回記述する必要があるのでしょうか。
container.each { ... }
開始反復子と終了反復子について言及せずに、次のようなものを記述します。常に終了イテレータを指定する必要があるのは少し冗長だと思います。
個人的に、私が使用する方法から外れる必要があるときはいつでも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
ので、使用するとコードが読みやすくなると言う人もいます。
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;
}
ですから、あなたに合ったものを選ぶのはあなた次第です。
アルゴリズム関数の多くと同様に、最初の反応は、ループよりもforeachを使用する方が読みにくいと考えることです。それは多くの炎上戦争のトピックでした。
あなたが慣用句に慣れると、あなたはそれが役に立つかもしれません。明らかな利点の1つは、コーダーにループの内部コンテンツを実際の反復機能から分離させることです。(わかりました、私はそれが利点だと思います。他の人はあなたが本当の利益なしにコードを切り刻んでいると言います)。
もう一つの利点は、私はforeachのを見たときに、私がいることである知っているすべてのアイテムのいずれかが処理されるか、例外がスローされますことを。
A のためのループは、ループを終了するためのいくつかのオプションを使用できます。ループを完全に実行させるか、breakキーワードを使用して明示的にループからジャンプするか、returnキーワードを使用してループ全体の関数を終了できます。対照的に、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;
}
}
}
std::for_each()
(つまり、この記事の時点で)、旧規格では、あなたが言うように、読みやすさを奨励し、途中でループを抜け出し禁止という名前のファンクタを使用する必要があります。しかし、同等のfor
ループには関数呼び出ししかなく、それも早すぎるブレークアウトを禁止します。しかし、それ以外は、範囲全体をstd::for_each()
強制することを言った点で優れていると思います。
あなたはほとんど正しいです:ほとんどの場合std::for_each
、純損失です。と比較for_each
するまで私は行きgoto
ます。goto
は、最も用途の広いフロー制御を提供します。これを使用して、想像できる他のあらゆる制御構造を事実上実装できます。ただし、その非常に汎用性があるということはgoto
、単独でinを表示しても、この状況で何をしようとしているのかについてほとんど何も知らないことを意味します。その結果、goto
最後の手段として使用する場合を除いて、右心の誰も使用しません。
標準的なアルゴリズムの中で、これfor_each
はほとんど同じ方法です-事実上すべてを実装するために使用できます。つまりfor_each
、この状況で何が使用されているかを確認しても、事実上何もわかりません。残念ながら、人々の態度for_each
はgoto
(たとえば)1970 年頃の態度に関するものです- 一部の人々は、それが最後の手段としてのみ使用されるべきであるという事実に気づきましたが、多くの人は依然としてそれを主要なアルゴリズムと考えています、そして他のものを使用することはめったにありません。ほとんどの場合、一目見ただけでも、代替案の1つが大幅に優れていることがわかります。
たとえば、人々がを使用してコレクションの内容を出力するためのコードを書いているのを見たことが何回あったのか、私はかなり確信を失っていfor_each
ます。私が見た投稿によると、これはの最も一般的な使用法の1つですfor_each
。彼らは次のようなものになります:
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
。
boost::mem_fn(&XXX::print)
はありませんXXX::print
std::cout
それが機能するための引数としてバインドする必要もある)。
ときに、より読みやすいbeeingてのための機能の書き込みの利点は、表示されないことがあります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);
あるずっと読みやすく超えます。
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
ループは、操作上の明確なセマンティクスをユーザコードからイテレータ(ループの実装方法の詳細)を非表示と定義することを意味する:各要素は、一度だけ繰り返されます。
現在の標準の可読性の問題は、コードのブロックの代わりに最後の引数としてファンクターを必要とすることです。そのため、多くの場合、特定のファンクタータイプを記述する必要があります。ファンクターオブジェクトはインプレースで定義できず(関数内で定義されたローカルクラスはテンプレート引数として使用できません)、ループの実装を実際のループから移動する必要があるため、これは可読性の低いコードになります。
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
、コードからボイラープレートイテレーターに関する多くの要素を取り除いた構造体を使用します。もっと複雑なものが必要で、実際の使用の数行上にファンクターを実装できない場合は、独自のループをロールします(操作を適切に維持します)。コードの重要ではないセクションでは、BOOST_FOREACHを使用する可能性があります(同僚に教えてもらった)
可読性とパフォーマンスの他に、一般的に見落とされる側面の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ループを実装または読み取る方法よりも、それについて心配したいと思います。
私は以前は嫌いでstd::for_each
、ラムダなしでは、それは完全に間違って行われたと思っていました。しかし、私は少し前に考えを変えました、そして今、私は実際にそれを愛しています。読みやすさも向上し、TDDの方法でコードを簡単にテストできるようになると思います。
std::for_each
アルゴリズムは次のように読み取ることができる範囲内のすべての要素を持つ何か、でき可読性を向上させます。実行したいアクションが20行で、アクションが実行される機能も20行程度だとします。これにより、従来のforループでは関数が40行長くなり、で約20行になるため、std::for_each
理解しやすくなります。
のファンクタはstd::for_each
より一般的であり、したがって再利用可能である可能性が高くなります。例:
struct DeleteElement
{
template <typename T>
void operator()(const T *ptr)
{
delete ptr;
}
};
そして、コードにstd::for_each(v.begin(), v.end(), DeleteElement())
は、明示的なループよりもわずかに優れたIMOのような1行しかありません。
これらの関数はすべて、通常、長い関数の途中で明示的なforループを実行するよりも単体テストを実施する方が簡単です。それだけでも、すでに大きなメリットがあります。
std::for_each
また、範囲を間違える可能性が少ないため、一般に信頼性が高くなります。
そして最後に、コンパイラーはstd::for_each
、特定の種類の手作りのforループよりもわずかに優れたコードを生成する可能性があります。これは、for_each がコンパイラーに対して常に同じに見えるため、コンパイラー作成者は知識をすべて入れて、それと同じようにすることができますできる。
同じことは、のような他のSTDのアルゴリズムに適用されるfind_if
、transform
など
for
各要素を繰り返すことができるforループまたは3つおきなどfor_each
は、各要素のみを繰り返すためのものです。その名前から明らかです。そのため、コードで何をしようとしているのかがより明確になります。
++
。珍しいかもしれませんが、forループも同じです。
transform
誰かを混乱させないために使う方が良いでしょう。
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
ますが、ここで何かを理解していることがわかった場合、それは定型ではありません。私は嫌い定型を。私はその存在に憤慨しています。それは本当のコードではありません、それを読むことによって学ぶことは何もありません、それはチェックを必要とするもう一つだけのものです。精神的な努力は、それをチェックするときに錆びやすくなることがどれだけ簡単かによって測定できます。
テンプレートは
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つのパラメーターのみを使用して独自の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ループの乱雑さが軽減されます。
もちろん、ケースはさまざまですが、これが私が通常最もよく見つけるものです。
イテレータを、ループの各反復で実行される関数の呼び出しにすることができます。
ここを参照してください:http : //www.cplusplus.com/reference/algorithm/for_each/
for_each
ません。その場合、それはその利点についての質問に答えません。
forループは壊れる可能性があります。私は、ここで彼のプレゼンテーションへのリンクですハーブサッターのためにオウムになりたいいけない: http://channel9.msdn.com/Events/BUILD/BUILD2011/TOOL-835T もコメントを必ずお読みくださいは:)
for_each
Fork-Joinパターンを実装できるようにします。それ以外はfluent-interfaceをサポートしています。
gpu::for_each
複数のワーカーでラムダタスクを呼び出すことにより、異機種並列コンピューティングにcuda / gpuを使用する実装を追加できます。
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);
std::for_each
と併用した場合、boost.lambda
またはboost.bind
読みやすさを向上できることが多い