27

ここでの質問と回答では、for_each()、transform() などよりも多くの「for」ループが反復子に見られるようです。Scott Meyers は、stl アルゴリズムが推奨されていることを示唆しているか、少なくとも 2001 年にはそうしていました。もちろん、それらを使用することは、多くの場合、ループ本体を関数または関数オブジェクトに移動することを意味します。これは容認できない合併症だと感じる人もいれば、問題をよりうまく解決できると考える人もいます.

それで...手巻きループよりもSTLアルゴリズムを優先する必要がありますか?

4

11 に答える 11

20

による:

  • 高性能が必要かどうか
  • ループの可読性
  • アルゴリズムが複雑かどうか

ループがボトルネックではなく、アルゴリズムが (for_each のように) 単純である場合、現在の C++ 標準では、読みやすさのために手巻きのループを使用したいと思います。(ロジックの局所性が重要です。)

ただし、C++0x/C++11 がいくつかの主要なコンパイラでサポートされるようになったので、STL アルゴリズムを使用することをお勧めします。STL アルゴリズムではラムダ式が許可され、ロジックの局所性が確保されるからです。

于 2008-09-25T18:42:23.077 に答える
10

私はここで逆行し、STL アルゴリズムをファンクターと共に使用すると、コードの理解と保守がはるかに容易になることを提唱しますが、それは正しく行う必要があります。読みやすさと明確さにもっと注意を払う必要があります。特に、ネーミングを正しくする必要があります。しかし、それを行うと、よりクリーンで明確なコードになり、より強力なコーディング手法へのパラダイム シフトが実現します。

例を見てみましょう。ここに子供たちのグループがあり、その「Foo Count」を何らかの値に設定したいと考えています。標準の for ループ、イテレータ アプローチは次のとおりです。

for (vector<Child>::iterator iter = children.begin();
    iter != children.end();
    ++iter)
{
    iter->setFooCount(n);
}

ええ、それはかなり明確で、間違いなく悪いコードではありません。ちょっと見ただけでわかる。しかし、適切なファンクターで何ができるか見てみましょう:

for_each(children.begin(), children.end(), SetFooCount(n));

うわー、それはまさに私たちが必要としているものです。それを理解する必要はありません。すべての子の「Foo Count」を設定していることはすぐにわかります。(.begin() / .end() のナンセンスが必要ない場合はさらに明確になりますが、すべてを取得することはできず、STL を作成するときに彼らは私に相談しませんでした。)

確かに、この魔法のファンクター を定義する必要はありますSetFooCountが、その定義はかなり定型的なものです。

class SetFooCount
{
public:
    SetFooCount(int n) : fooCount(n) {}

    void operator () (Child& child)
    {
        child.setFooCount(fooCount);
    }

private:
    int fooCount;
};

合計するとコードが増え、何が行われているかを正確に知るには別の場所を調べる必要がありますSetFooCount。しかし、適切な名前を付けたので、99% の確率で のコードを見る必要はありませんSetFooCount。私たちはそれが言うことをすると仮定し、for_each行を見るだけでよいのです。

私が本当に気に入っているのは、アルゴリズムを使用することがパラダイムシフトにつながるということです。リストをオブジェクトの集合と考えて、リストのすべての要素に対して処理を行うのではなく、リストをファーストクラスのエンティティと考え、リスト自体を直接操作します。for ループはリストを反復処理し、各要素でメンバー関数を呼び出して Foo カウントを設定します。代わりに、リスト内のすべての要素の Foo カウントを設定する 1 つのコマンドを実行しています。ささやかですが、木ではなく森を見ると、より力が湧いてきます。

したがって、少し考えて慎重に命名することで、STL アルゴリズムを使用して、よりクリーンで明確なコードを作成し、より細かいレベルで考え始めることができます。

于 2010-01-29T23:46:55.160 に答える
8

これstd::foreachは、何年も前に私にSTLを呪わせた種類のコードです。

それが良いかどうかはわかりませんが、ループのコードをループの前文のに置く方が好きです。私にとって、それは強い要件です。そして、そのstd::foreach構成ではそれができません(不思議なことに、JavaまたはC#のforeachバージョンは、私に関する限り、かっこいいです...したがって、ループ本体の局所性が非常に重要であることを確認していると思います)。

したがって、foreachを使用できるのは、読み取り可能で理解可能なアルゴリズムがすでに存在する場合のみです。そうでなければ、いや、私はしません。しかし、これは好みの問題だと思います。おそらく、このすべてを理解し、解析することを学ぶために、もっと一生懸命努力する必要があるからです...

ブーストの人々は、BOOST_FOREACHを書いたので、明らかに同じように感じたことに注意してください。

#include <string>
#include <iostream>
#include <boost/foreach.hpp>

int main()
{
    std::string hello( "Hello, world!" );

    BOOST_FOREACH( char ch, hello )
    {
        std::cout << ch;
    }

    return 0;
}

参照:http ://www.boost.org/doc/libs/1_35_0/doc/html/foreach.html

于 2008-09-25T20:14:34.527 に答える
6

これこそが、Scott Meyers が間違いを犯した唯一の点です。

必要なことに一致する実際のアルゴリズムがある場合は、もちろんそのアルゴリズムを使用します。

しかし、コレクションをループして各アイテムに何かを行うだけでよい場合は、コードを別のファンクターに分離しようとするのではなく、通常のループを実行するだけです。

boost::bind や boost::lambda などのオプションは他にもいくつかありますが、これらは非常に複雑なテンプレートのメタプログラミングであり、デバッグやコードのステップ実行ではうまく機能しないため、一般的には避ける必要があります。

他の人が述べたように、ラムダ式が一級市民になると、これはすべて変わります。

于 2008-09-25T19:06:21.523 に答える
4

forループは必須であり、アルゴリズムは宣言型です。あなたが書くときstd::max_element、あなたが何を必要とするかは明らかです、あなたが同じことを達成するためにループを使うとき、それは必ずしもそうではありません。

アルゴリズムはまた、わずかなパフォーマンスの優位性を持つことができます。たとえば、をトラバースするstd::deque場合、特殊なアルゴリズムにより、特定の増分がポインタをチャンク境界を越えて移動するかどうかを冗長にチェックすることを回避できます。

ただし、複雑なファンクター式を使用すると、アルゴリズムの呼び出しがすぐに判読できなくなります。明示的なループの方が読みやすい場合は、それを使用してください。アルゴリズム呼び出しを10階建てのバインド式なしで表現できる場合は、必ずそれを優先してください。ここでは、パフォーマンスよりも読みやすさが重要です。この種の最適化は、KnuthがHoareに非常に有名に帰するものだからです。ボトルネックに気づいたら、問題なく別の構成を使用できるようになります。

于 2008-09-25T20:13:29.380 に答える
3

私は基本的に STL アルゴリズムの大ファンですが、実際にはあまりにも面倒です。ファンクター/述語クラスを定義するまでに、2 行の for ループが 40 行以上のコードに変わり、突然 10 倍の理解が難しくなります。

ありがたいことに C++0x では、ラムダ関数autoと新しいfor構文を使用すると、作業が大幅に簡単になります。ウィキペディアでこのC++0x の概要を確認してください。

于 2008-09-26T01:01:01.887 に答える
3

It depends, if the algorithm doesn't take a functor, then always use the std algorithm version. It's both simpler for you to write and clearer.

For algorithms that take functors, generally no, until C++0x lambdas can be used. If the functor is small and the algorithm is complex (most aren't) then it may be better to still use the std algorithm.

于 2008-09-25T18:50:20.077 に答える
2

STLツールキットを直接(アルゴリズム用に)使用するとパフォーマンスがわずかに向上する可能性があるため、STLアルゴリズムのインターフェイスは最適ではないと思いますが、読みやすさ、保守性、さらには書き込み性が少しでも低下します。ツールの使用方法を再学習します。

ベクトル上のループの標準はどれだけ効率的ですか?

int weighted_sum = 0;
for (int i = 0; i < a_vector.size(); ++i) {
  weighted_sum += (i + 1) * a_vector[i];  // Just writing something a little nontrivial.
}

for_each構文を使用するよりも、またはこれを累積の呼び出しに適合させようとするよりも?

反復プロセスの効率は低いと主張することもできますが、for _ eachは、各ステップで関数呼び出しも導入します(これは、関数をインライン化しようとすることで軽減される可能性がありますが、「インライン」はコンパイラーへの提案にすぎないことに注意してください-無視する場合があります)。

いずれにせよ、違いはわずかです。私の経験では、作成するコードの90%以上はパフォーマンスにとって重要ではありません、コーダータイムにとって重要です。STLループをすべて文字通りインラインに保つことで、非常に読みやすくなります。自分自身または将来のメンテナにとって、つまずく間接参照は少なくなります。それがあなたのスタイルガイドにあるなら、あなたはあなたのコーダーのためにいくらかの学習時間を節約しています(それを認めてください、最初にSTLを正しく使うことを学ぶことはいくつかの落とし穴の瞬間を含みます)。この最後のビットは、私が書き込み可能性のコストによって意味するものです。

もちろん、いくつかの特殊なケースがあります。たとえば、実際には、for_each関数を分離して、他のいくつかの場所で再利用したい場合があります。または、パフォーマンスが非常に重要な数少ないセクションの1つである可能性があります。しかし、これらは特殊なケースであり、ルールではなく例外です。

于 2008-09-25T19:44:25.737 に答える
2

I wouldn't use a hard and fast rule for it. There are many factors to consider, like often you perform that certain operation in your code, is just a loop or an "actual" algorithm, does the algorithm depend on a lot of context that you would have to transmit to your function?

For example I wouldn't put something like

for (int i = 0; i < some_vector.size(); i++)
    if (some_vector[i] == NULL) some_other_vector[i]++;

into an algorithm because it would result in a lot more code percentage wise and I would have to deal with getting some_other_vector known to the algorithm somehow.

There are a lot of other examples where using STL algorithms makes a lot of sense, but you need to decide on a case by case basis.

于 2008-09-25T18:50:01.623 に答える
2

IMO、 std::for_each のような多くの標準ライブラリアルゴリズムは避けるべきです - 主に他の人が言及したラムダの欠如の問題のためだけでなく、詳細の不適切な隠蔽などがあるためです。

もちろん、関数やクラスに詳細を隠すことはすべて抽象化の一部であり、一般的にライブラリの抽象化は車輪を再発明するよりも優れています。しかし、抽象化の重要なスキルは、いつそれを行うべきか、いつそれを行うべきでないかを知ることです。過度の抽象化は、可読性や保守性などを損なう可能性があります。適切な判断は、柔軟性のないルールからではなく、経験によってもたらされます。もちろん、ルールを破ることを学ぶ前に、ルールを学ぶ必要があります。

OTOH、多くのプログラマーが長い間 C++ (およびそれ以前は C、Pascal など) を使用してきたという事実を考慮する価値があります。古い習慣はなかなか消えません。認知的不協和と呼ばれるものがあり、言い訳や合理化につながることがよくあります。ただし、結論を急がないでください。少なくとも、標準化担当者が決定後の不協和音を犯している可能性は高いです。

于 2009-10-02T03:27:34.387 に答える
0

大きな要因は、開発者の快適さのレベルだと思います。

おそらく、transform や for_each を使用するのが正しいことですが、それは効率的ではなく、手書きのループは本質的に危険ではありません。開発者が単純なループを記述するのに 30 分かかるのに対して、transform または for_each の構文を正しく取得し、提供されたコードを関数または関数オブジェクトに移動するのに半日かかる場合。そして、他の開発者は何が起こっているのかを知る必要があります。

新しい開発者は、エラーなしで一貫して使用できるため、手作りのループよりも、transform と for_each の使用方法を学習することによっておそらく最善のサービスを受けるでしょう。ループを書くことが第二の性質である私たちの残りの人にとっては、私たちが知っていることに固執し、空き時間にアルゴリズムに慣れるのがおそらく最善です.

このように言えば、手作りのループを for_each と transform の呼び出しに変換するのに 1 日を費やしたことを上司に話したとしても、上司はとても喜んでくれるとは思えません。

于 2008-09-25T19:17:16.597 に答える