web-dev-qa-db-ja.com

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

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

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

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

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

なぜもっと冗長なstd::generate C++ 11の範囲ベースのforループ上?

81
fredoverflow

最初のバージョン

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

一連の値を生成することを伝えます。

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

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

78
Bo Persson

Forループが範囲ベースであるかどうかにかかわらず、違いはありませんが、括弧内のコードが単純化されるだけです。アルゴリズムはintentを示すという点でより明確です。

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

_std::generate(numbers.begin(), numbers.end(), Rand);
_

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

私の最初の読み:

_for (int& x : numbers) x = Rand();
_

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

それらはかなり似ていますが、同一ではありません。私が最初のリーディングを挑発したいと思う可能性のある理由の1つは、このコードの最も重要な事実は、コードが範囲に割り当てられるということです。それで、あなたが「なぜ私がしたいのか...」があります。 C++では_std::generate_は「範囲割り当て」を意味するため、generateを使用します。 btwが_std::copy_を行うように、2つの違いは割り当て元です。

ただし、交絡要因があります。範囲ベースのforループには、反復子ベースのアルゴリズムよりも、範囲がnumbersであることを本質的に直接表現する方法があります。そのため、範囲ベースのアルゴリズムライブラリに取り組んでいます。boost::range::generate(numbers, Rand);は、_std::generate_バージョンよりも見栄えがします。

それに対して、範囲ベースのforループの_int&_はしわです。範囲の値の型がintではない場合、_generateコードのみであるのに対し、_int&_に変換できるかどうかに依存する厄介な微妙な処理をここで行っています。要素に割り当て可能なRandからの戻り値に依存します。値の型がintであっても、それがそうであるかどうかを考えるのをやめるかもしれません。したがって、autoは、何が割り当てられるかを見るまで、型について考えるのを遅らせます-_auto &x_を使用して、「範囲要素への参照を取得します。 C++ 03に戻ると、アルゴリズムは(関数テンプレートであるため)正確な型を非表示にするthe方法でしたが、現在はa方法です。

私は、最も単純なアルゴリズムが同等のループに比べてほんのわずかな利点しかないということは常に当てはまると思います。範囲ベースのforループは、ループを改善します(主にボイラープレートのほとんどを削除することにより、それより少しだけ多くなります)。そのため、マージンが厳しくなり、特定のケースではおそらく考えが変わります。しかし、まだスタイルの違いがあります。

30
Steve Jessop

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

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

my_util::generate(numbers, Rand);

意図の伝達と読みやすさの両方で、ループベースのforループに勝ると思います。


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

Herb Sutter:Lambdas、Lambdas Everywhere の次の例を考えてみましょう。

タスク:> xおよび< yであるvの最初の要素を検索します。

ラムダなし:

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; } );
23
Ali

myの意見では、手動ループは冗長性を低下させる可能性がありますが、読みやすくはありません:

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

初期化にこのループを使用しません1 numbersで定義された範囲。これを見ると、それは反復の範囲であるように見えますが、実際には(本質的に) )、つまり、範囲からreadingの代わりに、範囲に対してwritingです。

std::generateを使用すると、意図がより明確になります。

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

22
Nawaz

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

コンテナーをlimitまで(除外します。limitnumbersの有効なイテレーターです)、1つの分布からの変数と残りを別の分布からの変数で埋めます。

std::generate(numbers.begin(), limit, Rand1);
std::generate(limit, numbers.end(), Rand2);

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

9
Khaur

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

とはいえ、私にはstd :: algorithmを捨てない理由がもう1つあります。一部のデータ型に特化した特定のアルゴリズムがあります。

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

ここで効率化カードをプレイしようとしています。

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

6
Sergei Nosov

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

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

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

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

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

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

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

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

1
Tomek

注意すべきことの1つは、アルゴリズムは、どのようにではなく、何が行われるかを表すということです。

範囲ベースのループには、最初の要素から始めて、最後の要素まで適用して次の要素に移動するという方法が含まれます。単純なアルゴリズムでさえ別のことを行うことができ(少なくとも特定のコンテナーの過負荷で、恐ろしいベクトルについては考えていません)、少なくともそれが行われる方法はライタービジネスではありません。

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

1
Cedric