M2103-TP7-Exo-5

Les functors sont très largement utilisés dans la bibliothèque standard du C++, au point qu’il est illusoire de penser pouvoir se servir de cette bibliothèque si on n’a pas compris les functors.

Nous en montrerons quelques exemples dans des exercices ultérieurs.

Dans l’exercice présent, nous allons généraliser “encore plus” des algorithmes “généraux” que nous avons déjà écrits, comme par exemple des fonctions de tri, et ceci grâce aux functors.

Considérons le code suivant, inspiré de l’exercice Tri par sélection du TP sur les tris:

template <typename T, typename Iter_t>
void selectSort (Iter_t deb, Iter_t fin, 
                 bool (* isInf) (const T &, const T &))
{
    if (fin <= deb) return;

    for ( ; deb < fin - 1; ++deb)
    {
        Iter_t rgMin = deb;
        for (Iter_t j (deb + 1); j < fin; ++j) 
            if (isInf (*j, *rgMin)) rgMin = j;
        swap (*deb, *rgMin);
    }

} // selectSort()

Il s’agit de remplacer dans cette fonction le passage de la fonction isInf() par un functor.

Dans l’exercice précédent, le functor ITraitCar utilisé était abstrait (la fonction de comparaison était inconnue), mais il restait très spécifique : il ne pouvait comparer que des caractères.

Dans cet exercice, nous allons nous affranchir de cette limitation en rendant le functor générique

Travail à effectuer

Créer le projet LessThanAbstrGen.

Dans l’espace de noms anonyme du fichier LessThanAbstrGen.cpp, recopier la classe suivante :

    class Pers /* : public IEditable */
    {
        string   myNom;
        unsigned myAge;

      public :
        Pers (const string & nom, unsigned age)
              : myNom (nom), myAge (age) {}

        const string & getNom (void) const noexcept { return myNom; }
        unsigned       getAge (void) const noexcept { return myAge; }

      private :
        ostream & display (ostream & os)  const
        {
            return os << getAge () << " - " << getNom ();

        } // display()

      public :    // ajouté pour faciliter les essais
	  
        friend ostream & operator << (ostream & os, const Pers & p)
        {
            return p.display (os);
        }

    }; // Pers

Ecrire les deux classes TriParAgeAsc et TriParNomDesc, qui implémentent chacun l’opérateur () – les identificateurs sont suffisamment explicites – dont les deux paramètres sont des objets de la classe Pers et qui renvoie un booléen (ce qui correspond au profil d’un opérateur de comparaison).

La fonction selectSort() ne peut pas directement accepter comme dernier paramètre un functor d’une ou de l’autre classe, qui sont différentes.

Il est donc nécessaire de créer une classe plus générale qui puisse “représenter” n’importe lequel de ces deux functors, ou tout autre comparateur à venir.

Pour cela, ajouter la classe abstraite générique de functors ILessThanGen, classe d’interface qui ne contient que le profil de l’opérateur (), et en faire dériver publiquement les deux classes TriParAgeAsc et TriParNomDesc.

Dans la fonction selectSort() dont le code vous est donné, remplacer le second paramètre de généricité qui représente le type des valeurs à trier par le type du functor à utiliser pour la comparaison.

La seule contrainte (la spécification) est que ce paramètre “supporte” l’opérateur ().

Utiliser le code suivant pour tester vos functors :

    void testLessThanAbstrGen (void)
    {
        cout << "LessThanAbstrGen : \n";

        typedef vector <Pers> CVPers;
        typedef CVPers::size_type IndPers_t;
        CVPers vPers;

        vPers.push_back (Pers ("Charlotte", 21));
        vPers.push_back (Pers ("Alfred",    12));
        vPers.push_back (Pers ("Jean",      42));
        vPers.push_back (Pers ("Noemie",    11));
        vPers.push_back (Pers ("Berthe",    99));
        vPers.push_back (Pers ("Agathe",    29));
        vPers.push_back (Pers ("Sylvain",   42));
        vPers.push_back (Pers ("Pierre",    75));

        cout << "\nTri par age croissant\n\n";

        selectSort (vPers.begin(), vPers.end(), TriParAgeAsc());

        for (const Pers & personne : vPers)
            cout << personne << '\n';

        cout << "\nTri par nom decroissant\n\n";

        selectSort (vPers.begin (), vPers.end (), TriParNomDesc ());

        for (const Pers & personne : vPers)
            cout << personne << '\n';

    } // testLessThanAbstrGen()

Remarques

  1. Il n’est pas nécessaire d’instancier explicitement les deux comparateurs avant de les passer en paramètre de la fonction
    selectSort(), un simple appel aux constructeurs suffit (parce que le paramètre formel est une donnée :
    const).
  2. Comme on peut le constater, la fonction selectSort() peut maintenant trier n’importe quel type de données
    (généricité sur le type T) selon n’importe quel critère de tri (relation d’ordre).

    Il s’agit d’une nouvelle “jolie” utilisation du polymorphisme !

    Et en plus, on pourrait éventuellement récupérer des résultats intermédiaires du traitement, en mettant en œuvre la technique vue dans l’exercice précédent, qui consiste à charger le functor de données-membres !