web-dev-qa-db-fra.com

Quelle est la différence entre std :: transform et std :: for_each?

Les deux peuvent être utilisés pour appliquer une fonction à une gamme d'éléments.

Au plus haut niveau:

  • std::for_each ignore la valeur de retour de la fonction et garantit l’ordre d’exécution.
  • std::transform attribue la valeur de retour à l'itérateur et ne garantit pas l'ordre d'exécution.

Quand préférez-vous utiliser l'un par rapport à l'autre? Y at-il des mises en garde subtiles?

48
bendervader

std::transform est identique à map. L'idée est d'appliquer une fonction à chaque élément entre les deux itérateurs et d'obtenir un conteneur différent composé d'éléments résultant de l'application d'une telle fonction. Vous voudrez peut-être l'utiliser pour, par exemple, projeter le membre de données d'un objet dans un nouveau conteneur. Dans ce qui suit, std::transform est utilisé pour transformer un conteneur de std::strings dans un conteneur de 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();});

Par contre, vous exécutez std::for_each pour les seuls effets secondaires. En d'autres termes, std::for_each ressemble beaucoup à une simple boucle for.

Retour à l'exemple de chaîne:

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

En effet, à partir de C++ 11, il est possible d’obtenir la même chose avec une notation en utilisant des boucles for basées sur des intervalles:

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

Votre vue d'ensemble de haut niveau

  • std::for_each Ignore la valeur de retour de la fonction et garantit l'ordre d'exécution.
  • std::transform Assigne la valeur de retour à l'itérateur et ne garantit pas l'ordre d'exécution.

à peu près le couvre.

Une autre façon de voir les choses ( préférer l’une à l’autre);

  • Les résultats (la valeur de retour) de l'opération importent-ils?
  • L'opération sur chaque élément est-elle une méthode membre sans valeur de retour?
  • Y a-t-il deux plages d’entrée?

Une dernière chose à garder à l’esprit ( mise en garde subtile) est le changement dans les exigences des opérations de std::transform avant et après C++ 11 (de en.cppreference.com);

  • Avant C++ 11, ils devaient "ne pas avoir d’effets secondaires",
  • Après C++ 11, cela devient "ne doit pas invalider les itérateurs, y compris les itérateurs de fin, ni modifier les éléments des plages concernées".

Celles-ci devaient permettre essentiellement l'exécution indéterminée.

Quand dois-je utiliser l'un sur l'autre?

Si je veux manipuler chaque élément d'une plage, j'utilise for_each. Si je dois calculer quelque chose à partir de chaque élément, j'utiliserais alors transform. Lorsque vous utilisez for_each Et transform, je les couple normalement avec un lambda.

Cela dit, je trouve que mon utilisation actuelle du traditionnel for_each Est quelque peu amoindrie depuis l'avènement de la boucle basée sur la plage for et des lambdas en C++ 11 (for (element : range)) . Je trouve sa syntaxe et son implémentation très naturelles (mais votre kilométrage variera ici) et un ajustement plus intuitif pour certains cas d'utilisation.

19
Niall

Bien que la question ait reçu une réponse, je pense que cet exemple permettrait de clarifier davantage la différence.

for_each appartient à opérations STL non modificatives, ce qui signifie que ces opérations ne modifient ni les éléments de la collection ni la collection elle-même. Par conséquent, la valeur renvoyée par for_each est toujours ignorée et n'est pas affectée à un élément de la collection. Néanmoins, il est toujours possible de modifier des éléments de collection, par exemple lorsqu'un élément est passé à la fonction f à l'aide de la référence. Il faut éviter un tel comportement, car il n’est pas conforme aux principes du TSL.

En revanche, la fonction transform appartient à modification des opérations STL et applique les prédicats donnés (unary_op ou binary_op) aux éléments de la collection ou des collections et stocke les résultats dans une autre collection.

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

qui va imprimer:

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

Exemple concret d'utilisation de std :: tranform: lorsque vous souhaitez convertir une chaîne en majuscule, vous pouvez écrire du code comme celui-ci:

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

si vous essayez de faire la même chose avec std :: for_each comme:

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

Il ne le convertira pas en chaîne majuscule

1
mystic_coder