範囲ベースの「for」ループは、多くの単純なアルゴリズムを非推奨にしますか?


81

アルゴリズムソリューション:

std::generate(numbers.begin(), numbers.end(), rand);

範囲ベースのforループソリューション:

for (int& x : numbers) x = rand();

std::generateC ++ 11で範囲ベースのforループよりも詳細なforループを使用したいのはなぜですか?


14
構成可能性?気にしないでください。イテレータを使用したアルゴリズムは、とにかく構成できないことがよくあります... :(
R. Martinho Fernandes

2
...これはないbegin()end()
the_mandrill 2013年

6
@jrok今では多くの人rangeがツールボックスに機能を持っていると思います。(すなわちfor(auto& x : range(first, last))
R. Martinho Fernandes 2013

14
boost::generate(numbers, rand); // ♪
xeo 2013年

5
@JamesBrockこれについては、C ++チャットルームで頻繁に説明しました(トランスクリプトのどこかにあるはずです:P)。主な問題は、アルゴリズムが1つのイテレーターを返し、2つのイテレーターを使用することが多いことです。
R. Martinho Fernandes 2013

回答:


79

最初のバージョン

std::generate(numbers.begin(), numbers.end(), rand);

一連の値を生成することを通知します。

2番目のバージョンでは、読者はそれを自分で理解する必要があります。

タイピングの節約は、ほとんどの場合、読み取り時間で失われるため、最適ではありません。ほとんどのコードは、入力されるよりもはるかに多く読み取られます。


13
タイピングを節約しますか?わかりました。なぜ「コンパイル時のサニティチェック」と「キーボードのキーを押す」という同じ用語があるのですか?:)
fredoverflow 2013年

25
タイピングの節約は通常最適ではありません」ナンセンス。使用しているライブラリがすべてです。numbers理由もなく2回指定する必要があるため、std :: generateは長くなります。したがって:boost::range::generate(numbers, rand);。適切に構築されたライブラリに、より短くて読みやすいコードを含めることができない理由はありません。
ニコルボーラス2013年

9
それはすべて読者の目にあります。forループバージョンは、ほとんどのプログラミングの背景で理解できます。コレクションの各要素にrand値を設定します。Std :: generateには、最近のC ++を知っている必要があります。または、generateが実際には「生成された値を返す」ではなく、「アイテムを変更する」ことを意味すると推測する必要があります。
hyde

2
コンテナの一部だけを変更したい場合は、変更できstd::generate(number.begin(), numbers.begin()+3, rand)ますね。ですから、number2回指定すると便利な場合があると思います。
マーソンマオ2013

7
@MarsonMao:引数が2つしかない場合は、std::generate()代わりに、範囲の一部を柔軟に指定しながら、の繰り返しを削除するstd::generate(slice(number.begin(), 3), rand)ような架空の範囲スライス構文をstd::generate(number[0:3], rand)使用するnumberこともできます。3つの引数から始めて逆を行うのstd::generate()は、より面倒です。
嘘ライアン

42

forループが範囲ベースであるかどうかはまったく違いはありませんが、括弧内のコードを単純化するだけです。アルゴリズムは、意図を示すという点でより明確です。


30

個人的に、私の最初の読書:

std::generate(numbers.begin(), numbers.end(), rand);

は「範囲内のすべてに割り当てています。範囲はnumbersです。割り当てられる値はランダムです」です。

私の最初の読書:

for (int& x : numbers) x = rand();

「範囲内のすべてに何かを行っています。範囲はnumbersです。ランダムな値を割り当てることです。」

それらはかなり似ていますが、同一ではありません。私が最初の読みを誘発したいと思うかもしれない一つのもっともらしい理由は、このコードについての最も重要な事実はそれが範囲に割り当てられるということだと思うからです。だからあなたの「なぜ私がしたいのか...」があります。generateC ++ではstd::generate「範囲の割り当て」を意味するので使用します。ところでstd::copy、2つの違いは、割り当て元です。

ただし、交絡因子があります。範囲ベースのforループにはnumbers、イテレータベースのアルゴリズムよりも、範囲がであることを本質的に直接表現する方法があります。人々が範囲ベースのアルゴリズムライブラリで作業するのはそのためです。バージョンboost::range::generate(numbers, rand);よりも見栄えがしますstd::generate

それに対してint&、範囲ベースのforループにはしわがあります。範囲の値型がそうでない場合はどうなりますか?intここでは、変換可能であることに依存する厄介な微妙なことを行っていますint&が、generateコードrandは要素に割り当て可能からの戻りにのみ依存しています。値型がint、であっても、そうであるかどうかを考えるのをやめるかもしれません。したがってauto、何が割り当てられるかがわかるまで、タイプについて考えるのを延期しますauto &x。「タイプが何であれ、範囲要素を参照する」と言います。C ++ 03に戻ると、アルゴリズム(関数テンプレートであるため)は正確な型を非表示にする方法でしが、現在道。

最も単純なアルゴリズムには、同等のループに比べてわずかな利点しかありません。範囲ベースのforループは、ループを改善します(主にボイラープレートの大部分を削除しますが、それよりも少し多くなります)。そのため、マージンが狭くなり、特定の場合に気が変わってしまう可能性があります。しかし、そこにはまだスタイルの違いがあります。


operator int&()?が付いたユーザー定義型を見たことがありますか?:)
fredoverflow 2013年

@FredOverflowはに置き換えint&られSomeClass&、変換演算子とマークされていない単一パラメーターコンストラクターについて心配する必要がありますexplicit
templateRex

@FredOverflow:そうは思わないでください。だから、もしそれが起こったとしても、私はそれを期待していませんし、今それについてどんなに妄想的であっても、私がそれについて考えなければ、それは私を噛みます;-)プロキシオブジェクトは可能性がありますオーバーロードoperator int&()operator int const &() constで動作しますが、オーバーロードとで動作する可能性がoperator int() constありoperator=(int)ます。
スティーブジェソップ2013年

1
@rhalbersma:const以外の参照は一時的なものにバインドされないため、コンストラクターについて心配する必要はないと思います。参照型への変換演算子のみです。
スティーブジェソップ2013年

23

私の意見では、効果的なSTL項目43:「手書きのループよりもアルゴリズム呼び出しを優先する」。それでも良いアドバイスです。

私は通常、begin()/end()地獄を取り除くためにラッパー関数を作成します。これを行うと、例は次のようになります。

my_util::generate(numbers, rand);

意図の伝達と読みやすさの両方で、forループに基づく範囲を上回っていると思います。


そうは言っても、C ++ 98では、一部のSTLアルゴリズム呼び出しで発話できないコードが生成され、「手書きのループよりもアルゴリズム呼び出しを優先する」に従うのは良い考えではなかったことを認めなければなりません。幸いなことに、ラムダはそれを変えました。

ハーブサッターの次の例を考えてみましょう:Lambdas、LambdasEverywhere

タスク:あるVの最初の要素の検索> xとを< y

ラムダなし:

auto i = find_if( v.begin(), v.end(),
bind( logical_and<bool>(),
bind(greater<int>(), _1, x),
bind(less<int>(), _1, y) ) );

ラムダ付き

auto i=find_if( v.begin(), v.end(), [=](int i) { return i > x && i < y; } );

1
質問に少し直交します。最初の文だけが質問に対処します。
デビッド・ロドリゲス- dribeas

@DavidRodríguez-dribeasはい。後半は、項目43がまだ良いアドバイスだと思う理由を説明ています。
アリ

Boost.Lambdaを使用すると、C ++ラムダ関数を使用するよりもさらに優れています。autoi= find_if(v.begin()、v.end()、_ 1> x && _1 <y);
sdkljhdf hda 2013

1
ラッパーの場合は+1。同じことをします。1日目(またはおそらく2日目)から標準になっているはずです
Macke 2013

22

、私の冗長性を減らすかもしれませんが意見、マニュアルループは、readabitly欠けています:

for (int& x : numbers) x = rand();

このループを使用して、数値で定義された範囲を初期化することはしません1。これを見ると、数値の範囲で反復しているように見えますが、実際には(本質的に)そうではありません。読書範囲からの、範囲への書き込みです。

使用するときの意図ははるかに明確です std::generate

1.このコンテキストでの初期化とは、コンテナの要素に意味のある値を与えることを意味します。


5
ただし、範囲ベースのforループに慣れていないという理由だけではありませんか?このステートメントが範囲内の各要素に割り当てられていることは、私にはかなり明白に思えます。に精通している場合は、generateが同じことを行うことは明らかです。std::generateこれは、C ++プログラマーと見なすことができます(精通していない場合は、同じ結果で検索されます)。
スティーブジェソップ2013年

4
@SteveJessop:この答えは他の2つと違いはありません。読者の手間が少しかかり、エラーが発生しやすくなります(1&文字を忘れた場合はどうなりますか?)アルゴリズムの利点は、意図を示すのに対し、ループではそれを推測する必要があることです。ループの実装にバグがある場合、それがバグなのか意図的なものなのかは明確ではありません。
デビッド・ロドリゲス- dribeas

1
@DavidRodríguez-dribeas:この回答は他の2つ、IMOとは大きく異なります。それは、作者が一方のコードをもう一方よりも明確で理解しやすいと思う理由を掘り下げようとします。他の人は分析せずにそれを述べています。だから私はこれに答えるのに十分面白いと思う:
Steve Jessop

1
@SteveJessop:ループの本体を調べて、実際に数値を生成しているという結論に達する必要がありますが、の場合はstd::generate、単に見るだけで、この関数によって何かが生成されていると言えます。どのような何かをすることを関数に第三引数で答えています。これははるかに良いと思います。
nawaz 2013年

1
@SteveJessop:つまり、あなたは少数派に属しているということです。私は大多数にとってより明確なコードを書くでしょう:P。最後の1つ:他の人が私と同じようにループを読むとはどこにも言いませんでした。私は(むしろ言った意味、これは私に誤解されたループを読むための一つの方法である、とループ本体があるので、別のプログラマーが何が起こっているのかを把握するために、異なるそれを読むということ)。彼らは、さまざまな理由でそのようなループの使用に反対するかもしれませんが、それらはすべて彼らの認識に応じて正しい可能性があります。
nawaz 2013年

9

イテレータを入力として受け取るアルゴリズムでは、範囲ベースのループでは(単純に)実行できないことがいくつかあります。たとえばstd::generate

コンテナをlimit(除外され、limit上の有効なイテレータですnumbers)まで、ある分布の変数で満たし、残りを別の分布の変数で満たします。

std::generate(numbers.begin(), limit, rand1);
std::generate(limit, numbers.end(), rand2);

イテレータベースのアルゴリズムにより、操作している範囲をより適切に制御できます。


8
読みやすさの理由はアルゴリズムを好む大きな理由ですが、これは範囲ベースのforループアルゴリズムのサブセットにすぎず、したがって何も非推奨にできないことを示す唯一の答えです...
K-ballo

6

の特定のケースについてはstd::generate、読みやすさ/意図の問題に関する以前の回答に同意します。std :: generateは、私にはより明確なバージョンのようです。しかし、これはある意味で好みの問題であることを認めます。

そうは言っても、std :: algorithmを捨てないもう1つの理由があります-いくつかのデータ型に特化した特定のアルゴリズムがあります。

最も簡単な例はですstd::fill。一般バージョンは、指定された範囲でforループとして実装され、このバージョンはテンプレートをインスタンス化するときに使用されます。しかしいつもではない。たとえば、範囲を指定すると、std::vector<int>実際に呼び出されることがよくあります。memsetには内部でれ、はるかに高速で優れたコードが生成されます。

だから私はここで効率カードをプレイしようとしています。

手書きのループはstd :: Algorithmバージョンと同じくらい速いかもしれませんが、それより速くなることはほとんどありません。さらに、std :: Algorithmは特定のコンテナーとタイプに特化している場合があり、クリーンなSTLインターフェースの下で実行されます。


3

私の答えは多分そしていいえでしょう。C ++ 11について話している場合は、多分(いいえのように)。たとえばstd::for_each、ラムダを使用しても本当に面倒です。

std::for_each(c.begin(), c.end(), [&](ExactTypeOfContainedValue& x)
{
    // do stuff with x
});

ただし、範囲ベースのを使用する方がはるかに優れています。

for (auto& x : c)
{
    // do stuff with x
}

一方、C ++ 1yについて話している場合、いいえ、アルゴリズムはに基づく範囲によって廃止されることはないと私は主張します。C ++標準委員会には、C ++に範囲を追加する提案に取り組んでいる研究グループがあり、多形ラムダで行われている作業もあります。範囲を使用すると、イテレーターのペアを使用する必要がなくなり、多形ラムダを使用すると、ラムダの正確な引数タイプを指定できなくなります。これは、これを次のstd::for_eachように使用できることを意味します(これを難しい事実と見なさないでください。今日の夢は、まさにそのように見えます)。

std::for_each(c.range(), [](x)
{
    // do stuff with x
});

したがって、後者の場合、アルゴリズムの利点は[]、ラムダを使用してゼロキャプチャを指定することです。つまり、ループ本体を作成するだけの場合と比較して、コードのチャンクを、字句的に表示される変数ルックアップコンテキストから分離しました。分離は通常、読者にとって役立ち、読みながら考える必要はありません。
スティーブジェソップ2013年

1
キャプチャは重要ではありません。重要なのは、ポリモーフィックラムダを使用すると、xのタイプを明示的に説明する必要がないということです。
sdkljhdf hda 2013

1
その場合、この架空のC ++ 1yではfor_each、ラムダと一緒に使用しても意味がないように思われます。foreach + captionラムダは現在、範囲ベースのforループを記述する冗長な方法であり、少し冗長ではありませんが、ループよりもさらに冗長になります。for_eachもちろん、防御する必要があるとは思いませんが、あなたの答えを見る前でさえ、質問者がアルゴリズムを打ち負かしたいのであれば、彼for_eachはすべての可能なターゲットの中で最も柔らかいものとして選ぶことができたと思っていました;-)
Steve Jessop

防御するつもりはありませんfor_eachが、範囲ベースの場合に比べて1つの小さな利点があります-それを変換するためにparallel_をプレフィックスとして付けるだけで、より簡単に並列化できparallel_for_eachます(PPLを使用し、そうすることがスレッドセーフであると仮定する場合) 。:-D
sdkljhdf hda 2013

@lego sの実装がインターフェイスの背後に隠されており、任意に複雑(または任意に最適化)されている可能性があるという事実まで一般化すると、「小さな」利点は確かに「大きな」利点std::algorithmになります。
クリスチャンラウ2013年

1

注意すべきことの1つは、アルゴリズムが実行方法ではなく、実行内容を表現することです。

範囲ベースのループには、物事が行われる方法が含まれます。最初の要素から開始し、適用して、最後まで次の要素に進みます。単純なアルゴリズムでさえ、異なることを行う可能性があり(少なくとも特定のコンテナーのオーバーロード、恐ろしいベクトルについてさえ考えていません)、少なくともそれが行われる方法はライターの仕事ではありません。

私にとって、それは大きな違いであり、可能な限りカプセル化し、可能な場合はアルゴリズムを使用して文を正当化します。


1

範囲ベースのforループはまさにそれです。もちろん規格が変更されるまで。

アルゴリズムは関数です。パラメータにいくつかの要件を課す関数。要件は、たとえば、使用可能なすべての実行スレッドを利用して自動的に高速化する実装を可能にするために、標準で表現されています。

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