web-dev-qa-db-ja.com

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

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

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は、そのような単純な例がすでに非常に多くのコードを必要としていることを考えると、本当に価値があります。

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

10
fredoverflow

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

18
DeadMG

常に、目的の内容を最もよく表すバリアントを使用してください。あれは

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

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

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

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

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

今、他の可能性:

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

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

確かに、それは最初の解決策よりも見栄えが良いですが(少なくとも、ループbodyは柔軟で非常に明示的です)、それでも実際にははそうではありません すごい。これ以上の可能性がない場合は、これを使用しますが、おそらく...

より良い方法は?

for_eachに戻ります。文字通りfor_eachおよびと言って、実行される操作にも柔軟性があると便利ではないでしょうか。幸い、C++ 0xラムダなので、

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

多くの関連する状況に対する抽象的な一般的な解決策を見つけたので、この特定のケースでは絶対的な#1 favorite

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

本当にそれ以上に明確にすることはできません。ありがたいことに、C++ 0xは同様の構文built-inを取得します!

9
Dario

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

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

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

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

3
stijn

実際、C++ 0xを使用しても、確信が持てませんfor_eachは多くの愛を得るでしょう。

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

もっと読みやすいです。

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

2
Matthieu M.

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

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


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

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

2
Martin York

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

1
sarat

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

必要なことをより厳密に実行するアルゴリズムがある場合は、手書きのループよりもアルゴリズムを優先する必要があります。ここでの明らかな例は、sortまたはstable_sortを使用したソート、またはlower_boundupper_boundまたはequal_rangeを使用した検索ですが、ほとんどの場合、手動でコーディングしたループよりも使用することをお勧めします。

0
jk.

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

0

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

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

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

someCollection.forEach(someUnaryFunctor);

これの利点は、someCollectionが実際に実装されている方法やsomeUnaryFunctorが何をしているのかを知る必要がないことです。知っておく必要があるのは、そのforEachメソッドがコレクションのすべての要素を反復処理して、指定された関数に渡すことです。

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

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

0
back2dos