アルゴリズムソリューション:
std::generate(numbers.begin(), numbers.end(), rand);
範囲ベースのforループソリューション:
for (int& x : numbers) x = rand();
std::generate
C ++ 11で範囲ベースのforループよりも詳細なforループを使用したいのはなぜですか?
アルゴリズムソリューション:
std::generate(numbers.begin(), numbers.end(), rand);
範囲ベースのforループソリューション:
for (int& x : numbers) x = rand();
std::generate
C ++ 11で範囲ベースのforループよりも詳細なforループを使用したいのはなぜですか?
begin()
とend()
?
range
がツールボックスに機能を持っていると思います。(すなわちfor(auto& x : range(first, last))
)
boost::generate(numbers, rand); // ♪
回答:
最初のバージョン
std::generate(numbers.begin(), numbers.end(), rand);
一連の値を生成することを通知します。
2番目のバージョンでは、読者はそれを自分で理解する必要があります。
タイピングの節約は、ほとんどの場合、読み取り時間で失われるため、最適ではありません。ほとんどのコードは、入力されるよりもはるかに多く読み取られます。
numbers
理由もなく2回指定する必要があるため、std :: generateは長くなります。したがって:boost::range::generate(numbers, rand);
。適切に構築されたライブラリに、より短くて読みやすいコードを含めることができない理由はありません。
std::generate(number.begin(), numbers.begin()+3, rand)
ますね。ですから、number
2回指定すると便利な場合があると思います。
std::generate()
代わりに、範囲の一部を柔軟に指定しながら、の繰り返しを削除するstd::generate(slice(number.begin(), 3), rand)
ような架空の範囲スライス構文をstd::generate(number[0:3], rand)
使用するnumber
こともできます。3つの引数から始めて逆を行うのstd::generate()
は、より面倒です。
forループが範囲ベースであるかどうかはまったく違いはありませんが、括弧内のコードを単純化するだけです。アルゴリズムは、意図を示すという点でより明確です。
個人的に、私の最初の読書:
std::generate(numbers.begin(), numbers.end(), rand);
は「範囲内のすべてに割り当てています。範囲はnumbers
です。割り当てられる値はランダムです」です。
私の最初の読書:
for (int& x : numbers) x = rand();
「範囲内のすべてに何かを行っています。範囲はnumbers
です。ランダムな値を割り当てることです。」
それらはかなり似ていますが、同一ではありません。私が最初の読みを誘発したいと思うかもしれない一つのもっともらしい理由は、このコードについての最も重要な事実はそれが範囲に割り当てられるということだと思うからです。だからあなたの「なぜ私がしたいのか...」があります。generate
C ++では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&()
?が付いたユーザー定義型を見たことがありますか?:)
int&
られSomeClass&
、変換演算子とマークされていない単一パラメーターコンストラクターについて心配する必要がありますexplicit
。
operator int&()
とoperator int const &() const
で動作しますが、オーバーロードとで動作する可能性がoperator int() const
ありoperator=(int)
ます。
私の意見では、効果的な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; } );
で、私の冗長性を減らすかもしれませんが意見、マニュアルループは、readabitly欠けています:
for (int& x : numbers) x = rand();
このループを使用して、数値で定義された範囲を初期化することはしません1。これを見ると、数値の範囲で反復しているように見えますが、実際には(本質的に)そうではありません。読書範囲からの、範囲への書き込みです。
使用するときの意図ははるかに明確です std::generate
。
1.このコンテキストでの初期化とは、コンテナの要素に意味のある値を与えることを意味します。
std::generate
これは、C ++プログラマーと見なすことができます(精通していない場合は、同じ結果で検索されます)。
&
文字を忘れた場合はどうなりますか?)アルゴリズムの利点は、意図を示すのに対し、ループではそれを推測する必要があることです。ループの実装にバグがある場合、それがバグなのか意図的なものなのかは明確ではありません。
std::generate
、単に見るだけで、この関数によって何かが生成されていると言えます。どのような何かをすることを関数に第三引数で答えています。これははるかに良いと思います。
イテレータを入力として受け取るアルゴリズムでは、範囲ベースのループでは(単純に)実行できないことがいくつかあります。たとえばstd::generate
:
コンテナをlimit
(除外され、limit
上の有効なイテレータですnumbers
)まで、ある分布の変数で満たし、残りを別の分布の変数で満たします。
std::generate(numbers.begin(), limit, rand1);
std::generate(limit, numbers.end(), rand2);
イテレータベースのアルゴリズムにより、操作している範囲をより適切に制御できます。
の特定のケースについてはstd::generate
、読みやすさ/意図の問題に関する以前の回答に同意します。std :: generateは、私にはより明確なバージョンのようです。しかし、これはある意味で好みの問題であることを認めます。
そうは言っても、std :: algorithmを捨てないもう1つの理由があります-いくつかのデータ型に特化した特定のアルゴリズムがあります。
最も簡単な例はですstd::fill
。一般バージョンは、指定された範囲でforループとして実装され、このバージョンはテンプレートをインスタンス化するときに使用されます。しかしいつもではない。たとえば、範囲を指定すると、std::vector<int>
実際に呼び出されることがよくあります。memset
には内部でれ、はるかに高速で優れたコードが生成されます。
だから私はここで効率カードをプレイしようとしています。
手書きのループはstd :: Algorithmバージョンと同じくらい速いかもしれませんが、それより速くなることはほとんどありません。さらに、std :: Algorithmは特定のコンテナーとタイプに特化している場合があり、クリーンなSTLインターフェースの下で実行されます。
私の答えは多分そしていいえでしょう。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
});
[]
、ラムダを使用してゼロキャプチャを指定することです。つまり、ループ本体を作成するだけの場合と比較して、コードのチャンクを、字句的に表示される変数ルックアップコンテキストから分離しました。分離は通常、読者にとって役立ち、読みながら考える必要はありません。
for_each
、ラムダと一緒に使用しても意味がないように思われます。foreach + captionラムダは現在、範囲ベースのforループを記述する冗長な方法であり、少し冗長ではありませんが、ループよりもさらに冗長になります。for_each
もちろん、防御する必要があるとは思いませんが、あなたの答えを見る前でさえ、質問者がアルゴリズムを打ち負かしたいのであれば、彼for_each
はすべての可能なターゲットの中で最も柔らかいものとして選ぶことができたと思っていました;-)
for_each
が、範囲ベースの場合に比べて1つの小さな利点があります-それを変換するためにparallel_をプレフィックスとして付けるだけで、より簡単に並列化できparallel_for_each
ます(PPLを使用し、そうすることがスレッドセーフであると仮定する場合) 。:-D
std::algorithm
になります。