手書きのループよりもアルゴリズムを優先しますか?


10

次のうち、読みやすいものはどれですか。手書きのループ:

for (std::vector<Foo>::const_iterator it = vec.begin(); it != vec.end(); ++it)
{
    bar.process(*it);
}

またはアルゴリズムの呼び出し:

#include <algorithm>
#include <functional>

std::for_each(vec.begin(), vec.end(),
              std::bind1st(std::mem_fun_ref(&Bar::process), bar));

std::for_eachこのような単純な例を考えると、本当に多くのコードがすでに必要であるため、本当に価値があるのでしょうか。

この問題についてどう思いますか?


2
C ++の場合、答えは明白です。しかし、他の言語では、どちらもほぼ同じです(例:Python:。map(bar.process, vec)ただし、副作用のマップは推奨されておらず、マップよりもリスト内包表記/ジェネレータ式が推奨されます)。

5
そして、そこにもBOOST_FOREACH...
ベンジャミンバニエ

それで、有効なSTLの項目はあなたを説得しませんでしたか? stackoverflow.com/questions/135129/...
仕事

回答:


18

ラムダが導入されたのには理由があり、それは標準委員会でさえ2番目の形式が悪いと認識しているためです。C ++ 0xとラムダのサポートが得られるまで、最初の形式を使用します。


2
同じ理由を使用して、2番目の形式を正当化できます。標準委員会は、ラムダを導入してそれをさらに改善するほど優れていることを認識しました。:-p(それでも+1)
Konrad Rudolph

2つ目はそれほど悪いとは思わない。しかし、それはより良い場合があります。たとえば、operator()を追加しないことによって、意図的にBarオブジェクトを使いにくくしました。これを使用することで、ループ内の呼び出しポイントが大幅に簡素化され、コードが整理されます。
マーティンヨーク

注:ラムダのサポートは、2つの主な不満のために追加されました。1)ファンクタにパラメータを渡すために必要な標準のボイラープレート。2)呼び出しポイントにコードがない。メソッドを呼び出すだけなので、コードはこれらのどちらも満たしません。したがって、1)パラメータを渡すための追加のコードはありません。2)コードはすでに呼び出しポイントにないため、ラムダはコードの保守性を向上させません。だからラムダは素晴らしい追加です。これは彼らにとって良い議論ではありません。
マーティンヨーク

9

常に、意図することを最もよく表すバリアントを使用してください。あれは

の各要素xに対してvec、を実行しますbar.process(x)

では、例を見てみましょう:

std::for_each(vec.begin(), vec.end(),
              std::bind1st(std::mem_fun_ref(&Bar::process), bar));

私たちもfor_eachそこにいます- イッペ[begin; end)操作したい範囲があります。

原則として、アルゴリズムははるかに明示的であり、手書きの実装よりも優れていました。しかし、その後 ...バインダー?Memfun?基本的にメンバー関数を取得する方法のC ++内部?私の仕事では、私はそれらを気にしません!また、この冗長で不気味な構文に悩まされたくありません。

今、他の可能性:

for (std::vector<Foo>::const_iterator it = vec.begin(); it != vec.end(); ++it)
{
    bar.process(*it);
}

確かに、これは認識すべき一般的なパターンですが、...イテレータの作成、ループ、インクリメント、逆参照です。これらも、私の仕事を成し遂げるために私が気にしないすべてのものです。

確かに、それは(少なくとも、ループwaayより良い最初のソリューションよりも見える体が柔軟かつきわめて明確である)、それでも、それは本当にないことを素晴らしいです。可能性がなければこれを使用しますが、多分...

より良い方法は?

に戻りfor_eachます。文字通り言うのは素晴らしいことではないかfor_each も行う必要がある操作に柔軟であること?幸い、C ++ 0xラムダなので、

for_each(v.begin(), v.end(), [&](const Foo& x) { bar.process(x); })

多くの関連する状況に対する抽象的な一般的なソリューションが見つかったので、この特定のケースでは、絶対的な#1のお気に入りがあることに注意してください。

foreach(const Foo& x, vec) bar.process(x);

本当にそれ以上に明確にすることはできません。ありがたいことに、C ++ 0xは同様の構文が組み込まれています。


2
「類似した構文」とはfor (const Foo& x : vec) bar.process(x);、または必要にconst auto&応じて使用することです。
Jon Purdy、2011年

const auto&可能です?それを知らなかった-素晴らしい情報!
ダリオ

8

これはとても読みにくいので?

for (unsigned int i=0;i<vec.size();i++) {
{
    bar.process(vec[i]);
}

4
申し訳ありませんが、なんらかの理由で、コードがどこにあるべきかと私が考える「検閲済み」と表示されています。
Mateen Ulhaq、2011年

6
とを比較するintsize_tは危険なので、コードはコンパイラ警告を出します。また、たとえば、すべてのコンテナでインデックスが機能するわけではありませんstd::set
fredoverflow 2011年

2
このコードは、インデックス演算子がいくつかあるコンテナでのみ機能します。アルゴリズムを不必要に拘束します。マップ<>が適しているとしたらどうでしょうか オリジナルのforループとfor_eachは影響を受けません。
JBRウィルキンソン

2
また、ベクトルのコードを何回設計してから、それをマップと交換することにしましたか?そのためのデザインが言語の優先事項であるかのように。
Martin Beckett

2
一部のコレクションはインデックス作成のパフォーマンスが低いため、コレクションをインデックスで繰り返し処理することはお勧めしませんが、イテレータを使用するとすべてのコレクションが適切に動作します。
slaphappy

3

一般に、最初の形式は、どんなバックグラウンドを持っているかに関係なく、forループが何であるかを知っているほとんどの人が読むことができます。

また、一般的に、2番目のものはそれほど読みやすいものではありません。for_eachが何をしているのかを十分に理解するのは簡単ですが、見たことがない場合は、std::bind1st(std::mem_fun_ref理解するのが難しいと想像できます。


2

実は、C ++ 0xでも、どれだけfor_each愛されるかはわかりません。

for(Foo& foo: vec) { bar.process(foo); }

より読みやすくなります。

アルゴリズム(C ++の場合)で私が嫌いなのは、イテレータを推論し、非常に冗長なステートメントを作成することです。


2

あなたが関手としてbarを書いたなら、それははるかに簡単です:

// custom functor
class Bar
{    public: void operator()(Foo& value) { /* STUFF */ }
};


std::for_each(vec.begin(), vec.end(), Bar());

ここでコードは非常に読みやすいです。


2
ファンクタを書いたという事実は別として、それはかなり読みやすいです。
ウィンストンエワート、2011

このコードがもっと悪いと思う理由については、こちらをご覧ください。
sbi

1

後者はすっきりとしていてきれいなので、私は後者を好みます。実際には他の多くの言語の一部ですが、C ++ではライブラリの一部です。それは本当に重要ではありません。


0

今、この問題は実際にはC ++に固有のものではないと私は主張します。

問題は、いくつかのファンクタをさらに別の関数に渡すことは、ループを置き換えることではありません。すぐに頭に浮かぶ訪問者観察者
などの関数型プログラミングで非常に自然になる特定のパターンを単純化することになっています。 一次関数が不足している言語(Javaがおそらく最良の例です)では、このようなアプローチでは常に、特定のインターフェースを実装する必要があります。

他の言語でよく見られる一般的な使用法は次のとおりです。

someCollection.forEach(someUnaryFunctor);

これの利点は、someCollection実際にどのように実装されているのか、何someUnaryFunctorが行われているのかを知る必要がないことです。あなたが知る必要があるのは、そのforEachメソッドがコレクションのすべての要素を繰り返し、それらを指定された関数に渡すことです。

個人的には、あなたがその立場にいる場合、反復したいデータ構造に関するすべての情報と、すべての反復ステップで何をしたいかについてのすべての情報を得るために、特に言語において、機能的アプローチは物事を複雑にしています。これは一見かなり退屈です。

また、forループにはない、一定のコストで呼び出される呼び出しが多数あるため、関数型のアプローチは低速になることにも注意してください。


1
「また、forループにはない、特定のコストで実行される呼び出しが多数あるため、関数型のアプローチは遅くなることにも留意してください。」?このステートメントはサポートされていません。特に内部で最適化が行われる可能性があるため、機能的アプローチは必ずしも遅くなるとは限りません。また、ループを完全に理解している場合でも、ループはより抽象的な形できれいに見えるでしょう。
Andres F.

@Andres F:だからこれはもっときれいに見えますか?std::for_each(vec.begin(), vec.end(), std::bind1st(std::mem_fun_ref(&Bar::process), bar));また、実行時またはコンパイル時(運が良ければ)は遅くなります。フロー制御構造を関数呼び出しで置き換えるとコードが改善される理由はわかりません。ここに提示された代替案については、より読みやすくなっています。
back2dos

0

ここでの問題はfor_each、それが実際にはアルゴリズムではなく、手書きのループを書くこととは異なる(そして通常は劣る方法)だと思います。この観点から、これは最も使用頻度の低い標準アルゴリズムである必要があり、はい、おそらくforループを使用することもできます。ただし、より具体的な用途に合わせて調整されたいくつかの標準アルゴリズムがあるため、タイトルのアドバイスは依然として有効です。

必要なことをより厳密に実行するアルゴリズムがある場合は、手書きのループよりもアルゴリズムを優先する必要があります。ここで明らかな例がでソートされているsortか、stable_sortまたはで検索lower_boundupper_boundまたはequal_range、それらのほとんどは、彼らが手コード化されたループの上使用することが好ましいされたいくつかの状況があります。


0

この小さなコードスニペットでは、どちらも同じように読みやすくなっています。一般に、手動ループはエラーが発生しやすく、アルゴリズムの使用を好みますが、実際には具体的な状況に依存します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.