web-dev-qa-db-ja.com

std :: transformとstd :: for_eachの違いは何ですか?

両方を使用して、ある範囲の要素に関数を適用できます。

高レベルで:

  • std::for_eachは、関数の戻り値を無視し、実行の順序を保証します。
  • std::transformは、戻り値を反復子に割り当てますが、実行順序を保証しません。

どちらを使用するのが望ましいのですか?微妙な注意事項はありますか?

48
bendervader

std::transformmapと同じです。このアイデアは、2つのイテレータの間にある各要素に関数を適用し、そのような関数の適用から生じる要素で構成される別のコンテナを取得することです。たとえば、オブジェクトのデータメンバーを新しいコンテナに投影するために使用できます。以下では、std::transformを使用して、std::stringsのコンテナー内のstd::size_tsのコンテナーを変換します。

std::vector<std::string> names = {"hi", "test", "foo"};
std::vector<std::size_t> name_sizes;

std::transform(names.begin(), names.end(), std::back_inserter(name_sizes), [](const std::string& name) { return name.size();});

一方、唯一の副作用のためにstd::for_eachを実行します。つまり、std::for_eachは、範囲ベースの単純なforループに非常に似ています。

文字列の例に戻ります。

std::for_each(name_sizes.begin(), name_sizes.end(), [](std::size_t name_size) {
    std::cout << name_size << std::endl;
});

実際、C++ 11以降では、範囲ベースのforループを使用した簡潔な表記法で同じことが実現できます。

for (std::size_t name_size: name_sizes) {
    std::cout << name_size << std::endl;
}
48
Ilio Catallo

高レベルの概要

  • _std::for_each_は、関数の戻り値を無視し、実行の順序を保証します。
  • _std::transform_は戻り値を反復子に割り当てますが、実行の順序を保証しません。

ほぼカバーしています。

別の見方(どちらかを優先する);

  • 操作の結果(戻り値)は重要ですか?
  • 各要素に対する操作は、戻り値のないメンバーメソッドですか?
  • 入力範囲は2つありますか?

留意すべきもう1つの点(微妙な注意点)は、C +の前後の _std::transform_ の操作の要件の変更です。 +11(en.cppreference.comから);

  • C++ 11以前は、「副作用がない」必要がありましたが、
  • C++ 11以降、これは「終了イテレータを含むイテレータを無効にしたり、関係する範囲の要素を変更したりすることはできません」に変更されました。

基本的に、これらは未決定の実行順序を許可することでした。

一方を他方の上で使用するのはいつですか?

範囲内の各要素を操作する場合は、_for_each_を使用します。各要素から何かを計算する必要がある場合は、transformを使用します。 _for_each_およびtransformを使用する場合、通常はそれらをラムダとペアにします。

そうは言っても、C++ 11での範囲ベースのforループとラムダ(for (element : range))の出現以来、従来の_for_each_の現在の使用はやや減少していることがわかります。その構文と実装は非常に自然であり(ただし、ここでの距離は異なります)、いくつかのユースケースにもっと直感的にフィットします。

19
Niall

質問は回答されましたが、この例によって違いがさらに明確になると思います。

for_each非変更STL操作に属します。つまり、これらの操作はコレクションの要素またはコレクション自体を変更しません。したがって、for_eachによって返される値は常に無視され、コレクション要素に割り当てられません。それでも、たとえば参照を使用して要素がf関数に渡される場合など、コレクションの要素を変更することは可能です。 STLの原則と一致しないため、このような動作は避けてください。

対照的に、transform関数はSTL操作の変更に属し、特定の述語(unary_opまたはbinary_op)をコレクションの要素に適用し、結果を別のコレクションに格納します。

#include <vector>
#include <iostream>
#include <algorithm>
#include <functional>
using namespace std;

void printer(int i) {
        cout << i << ", ";
}
int main() {
    int mynumbers[] = { 1, 2, 3, 4 };
    vector<int> v(mynumbers, mynumbers + 4);

    for_each(v.begin(), v.end(), negate<int>());//no effect as returned value of UnaryFunction negate() is ignored.
    for_each(v.begin(), v.end(), printer);      //guarantees order

    cout << endl;

    transform(v.begin(), v.end(), v.begin(), negate<int>());//negates elements correctly
    for_each(v.begin(), v.end(), printer);
    return 0;
}

印刷されます:

1, 2, 3, 4, 
-1, -2, -3, -4, 
13
BugShotGG

Std :: tranformの実際の使用例は、文字列を大文字に変換する場合です。次のようなコードを記述できます。

std::transform(s.begin(), s.end(), std::back_inserter(out), ::toupper);

次のようなstd :: for_eachで同じことを達成しようとする場合:

std::for_each(s.begin(), s.end(), ::toupper);

大文字の文字列に変換しません

1
mystic_coder