loop for examples c++ c++11 stl-algorithm

c++ - examples - ¿Cuál es la diferencia entre std:: transform y std:: for_each?



foreach c++ 11 (4)

Ambos pueden usarse para aplicar una función a un rango de elementos.

En un alto nivel:

  • std::for_each ignora el valor de retorno de la función y garantiza el orden de ejecución.
  • std::transform asigna el valor de retorno al iterador y no garantiza el orden de ejecución.

¿Cuándo prefieres usar el uno contra el otro? ¿Hay alguna advertencia sutil?


Aunque la pregunta ha sido respondida, creo que este ejemplo aclararía aún más la diferencia.

for_each pertenece a las operaciones STL no modificadoras , lo que significa que estas operaciones no cambian los elementos de la colección o la colección en sí. Por lo tanto, el valor devuelto por for_each siempre se ignora y no se asigna a un elemento de colección . No obstante, todavía es posible modificar elementos de colección, por ejemplo, cuando un elemento se pasa a la función f utilizando la referencia. Uno debe evitar tal comportamiento ya que no es consistente con los principios de STL.

Por el contrario, la función de transform pertenece a la modificación de operaciones STL y aplica predicados dados (unary_op o binary_op) a elementos de la colección o colecciones y almacena resultados en otra colección.

#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; }

que se imprimirá:

1, 2, 3, 4, -1, -2, -3, -4,


Ejemplo real de usar std :: tranform es cuando quieres convertir una cadena en mayúscula, puedes escribir código como este:

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

si intentas lograr lo mismo con std :: for_each como:

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

No lo convertirá en una cadena en mayúsculas


Su visión general de alto nivel

  • std::for_each ignora el valor de retorno de la función y garantiza el orden de ejecución.
  • std::transform asigna el valor de retorno al iterador y no garantiza el orden de ejecución.

prácticamente lo cubre.

Otra forma de verlo ( preferir uno sobre el otro );

  • ¿Importan los resultados (el valor de retorno) de la operación?
  • ¿La operación en cada elemento es un método miembro sin valor de retorno?
  • ¿Hay dos rangos de entrada?

Una cosa más a tener en cuenta ( advertencia sutil ) es el cambio en los requisitos de las operaciones de std::transform antes y después de C ++ 11 (de en.cppreference.com);

  • Antes de C ++ 11, se les exigía que "no tuvieran ningún efecto secundario",
  • Después de C ++ 11, esto cambió a "no debe invalidar ningún iterador, incluidos los iteradores finales, ni modificar ningún elemento de los rangos involucrados"

Básicamente, estos debían permitir el orden de ejecución indeterminado.

¿Cuándo uso uno sobre el otro?

Si quiero manipular cada elemento en un rango, entonces uso for_each . Si tengo que calcular algo de cada elemento, entonces usaría la transform . Cuando uso for_each y transform , normalmente los for_each con un lambda.

Dicho esto, encuentro que mi uso actual del tradicional for_each está algo disminuido desde el advenimiento del rango basado for bucles y lambdas en C ++ 11 ( for (element : range) ). Encuentro que su sintaxis y su implementación son muy naturales (pero su millaje aquí variará) y un ajuste más intuitivo para algunos casos de uso.


std::transform es lo mismo que map . La idea es aplicar una función a cada elemento entre los dos iteradores y obtener un contenedor diferente compuesto por elementos resultantes de la aplicación de dicha función. Puede usarlo para, por ejemplo, proyectar el miembro de datos de un objeto en un contenedor nuevo. A continuación, std::transform se usa para transformar un contenedor de std::string s en un contenedor de std::size_t s.

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();});

Por otro lado, ejecuta std::for_each para los únicos efectos secundarios. En otras palabras, std::for_each se asemeja mucho a un bucle for base simple.

Volver al ejemplo de la cadena:

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

De hecho, a partir de C ++ 11 se puede lograr lo mismo con una notación de terser usando un rango basado for bucles:

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