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
- 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
). - Comme on peut le constater, la fonction
selectSort()
peut maintenant trier n’importe quel type de données
(généricité sur le typeT
) 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 !