Participer au site avec un Tip
Rechercher
 

Améliorations / Corrections

Vous avez des améliorations (ou des corrections) à proposer pour ce document : je vous remerçie par avance de m'en faire part, cela m'aide à améliorer le site.

Emplacement :

Description des améliorations :

Fonction std::for_each

Entête à inclure

#include <algorithm>

Fonction std::for_each

template <typename IT, typename FUNCTION_OBJECT>
FUNCTION_OBJECT std::for_each( IT begin, IT end, FUNCTION_OBJECT fo );

Cette fonction permet d'appliquer un même traitement à un ensemble d'éléments d'une collection. Par l'aspect générique de cette fonction, elle peut être utilisées sur tous types de collections comptatibles avec le modèle STL (std::array, std::vector, std::list, ...).

Afin de correctement comprendre le fonctionnement de la fonction for_each, il nécessaire d'avoir assimilé le concept de « function object » proposé par la librairie STL. En fait ce concept n'est pas un concept C++, mais c'est bien la librairie STL qui l'introduit. Pour comprendre ce concept, regardons l'extrait de code suivant : il correspond à une possibilité d'écriture de la fonction for_each (le « function object » correspond au troisième paramètre de la fonction).

template <typename IT, typename FUNCTION_OBJECT>
inline FUNCTION_OBJECT for_each( IT current, IT end, FUNCTION_OBJECT fo ) {
    while( current != end ) {
        fo( *current ++ );
    }
    return fo;
}

L'idée est simple : un « function object » est un élément sur lequel on peut chercher à lancer un appel en utilisant les parenthèses. Deux alternatives peuvent être envisagées :

  • soit on passe un pointeur sur une fonction (et donc l'utilisation de parenthèses lancera l'appel).
  • soit on passe une instance et dans ce cas, l'utilisation de patenthèses cherchera à invoquer l'opérateur (). Cette possibilité, bien que peut-être surprenante, est tout à fait légale en C++.

Vous comprenez donc mieux l'origine du nom « function object ». Pour la première solution, je pense qu'elle se passe de commentaire. Par contre, la seconde justifie le petit exemple qui suit.

#include <iostream>
#include <string>

using namespace std;

class Demo {
public:
    void operator()( const string & parameter ) {
        cout << "in operator() with " << parameter << endl;
    }
};

int main() {

    Demo instance;
    instance( "short syntax" );
    instance.operator()( "expanded syntax" );
    
    return 0;
}

Et voici les résultats produits par cet exemple.

$> g++ -o Sample Sample.c -std=c++11
$> ./Sample
in operator() with short syntax
in operator() with expanded syntax
$>

Pourquoi accepter ces deux possibilités (fonction ou objet) ? Pour répondre de manière adaptée à différents scénario. Si chaque éléments à traiter peut l'être indépendemment des autres, alors passer un pointeur sur fonction sera très certainement la meilleure solution étant donné que c'est la plus simple à coder.

Par contre, si un contexte doit être conservé entre chaque appel, alors l'utilisation d'un objet sera plus utile : effectivement, l'objet pourra porter votre contexte par le biais de ses attributs. A chaque exécution de l'opérateur (), le contexte sera très facilement accessible.

Paramètres

  • begin : un itérateur qui cible le premier élément de la collection à affecter à la valeur considérée.
  • end : un itérateur placé après le dernier élément de la collection à affecter à la valeur considérée.
  • fo : la fonction à appliquer ou une instance de classe exposant un opérateur ().

Valeur de retour

Cette fonction renvoie le « function object » passé en paramètre.

Exemple de code

#include <algorithm>
#include <iostream>
#include <iterator>
#include <list>
#include <string>

using namespace std;

/* std::for_each implementation is like this.

template <typename IT, typename FUNCTION_OBJECT>
inline FUNCTION_OBJECT for_each2( IT current, IT end, FUNCTION_OBJECT fo ) {
    while( current != end ) {
        fo( *current ++ );
    }
    return fo;
}

*/

void charToUpper( char & c ) {
    if ( c>='a' && c<='z' ) {
        c &= 223;
    }
}

class StringToUpper {
    int cpt = 0;
public:
    
    inline void operator()( string & str ) {
        cout << "Not a function, but an instance" << endl;
        cpt++;
        str[0] = cpt + 0x30;
        for_each( str.begin(), str.end(), charToUpper );
    }
};

int main() {

    list<string> maListe;
    maListe.push_front( "x_C++" );
    maListe.push_front( "x_Python" );
    maListe.push_back(  "x_Java" );
    maListe.push_back(  "x_C Sharp" );

    for_each( maListe.begin(), maListe.end(), StringToUpper() );
    copy( maListe.begin(), maListe.end(),
          ostream_iterator<string>(cout," - ") );
    cout << endl;
    
    return 0;
}

ATTENTION : cet exemple utilise une nouveauté introduite avec la version 2011 du standard C++ (l'initialisation de l'attribut lors de sa déclaration). Avec la chaine de compilation g++, il est donc nécessaire d'ajouter l'option -std=c++11 pour correctement compiler cet exemple. Voici les résultats produits par l'exemple précédent.

$> g++ -o Sample Sample.c -std=c++11
$> ./Sample
Not a function, but an instance
Not a function, but an instance
Not a function, but an instance
Not a function, but an instance
1_PYTHON - 2_C++ - 3_JAVA - 4_C SHARP - 
$> 

Sujets connexes