Le texte qui suit est pertinent avec C++ 03, mais l'est moins avec C++ 11. Pour un texte plus actuel, voir composition_fonctions.html.
Si vous ne l'avez pas encore lue, commencez par la partie 00 de ce document. La partie 02, quant à elle, suivra.
Maintenant que nous avons construit un foncteur permettant la composition sous forme f(g(x)) de deux opérations f et g, que nous avons constaté que la forme résultante se prête à être utilisée de concert avec des algorithmes standards, et maintenant que nous avons vu que le résultat comporte quelques déficiences, nous allons procéder à un premier raffinement de notre modèle.
Réexaminons la forme exprimée à droite, qui est un condensé de la version cumulant la somme des carrés d'une séquence de valeurs et de celle démontrant que notre approche se prête à être mise en application à l'aide d'algorithmes standards. L'écriture adoptée ici repose sur le fait qu'un cumuler<T> reçoit un T& à la construction et cumule les valeurs dans une variable dont il n'est pas responsable (ci, la variable cumul déclarée dans main()). Cette approche fonctionne, mais manque d'élégance. Le code client a une apparence quelque peu étrange, et ne respecte pas les usages en vigueur dans le monde de STL et de la bibliothèque standard de C++ en général. Prenons par exemple l'algorithme for_each(). Celui-ci prend ses trois paramètres par valeur, utilise à l'interne une copie de l'opération que représente le troisième paramètre (ici, notre foncteur composite), et retourne une copie de l'opération suite à son exécution. |
|
D'ailleurs, si nous revenons à l'écriture initiale du foncteur cumuler<T>, nous retrouverons une écriture plus naturelle, où le foncteur cumule des valeurs dans un attribut dont il est pleinement responsable, et pouvant être assujetti à une sémantique de copie plus naturelle. Le programme principal proposé à droite, d'ailleurs, est d'apparence plus naturelle que ceux reposant sur notre approche par foncteur composite. Un cumuler<int> par défaut y est passé à for_each(), qui l'utilise pour cumuler les valeurs dans le tableau, et qui retourne le fruit du cumul dont la méthode valeur() est ensuite invoquée pour afficher le résultat. Évidemment, nous avons perdu au change le volet composite qui nous permettait de réaliser la somme des carrés des valeurs plutôt que la somme des valeurs tout court. Nous allons maintenant faire un premier pas vers une harmonisation des syntaxes et des formes. |
|
compose_fx_gx.h (version 01a) | |
Une adaptation simple à faire pour amener notre foncteur composite à un niveau de convivialité comparable à celui des foncteurs simples est de lui ajouter des opérateurs de conversion implicite en FX et en GX, qui retourneraient respectivement sa partie fx_ et sa partie gx_. Cette version a un grand mérite : elle est très conviviale à l'utilisation pour les cas les plus simples. En effet, avec cette approche, il est possible de traiter un composite comme un FX ou comme un GX de manière essentiellement immédiate, dans la mesure où l'écriture n'est pas ambiguë :
|
|
Un exemple de programme reposant sur cette manoeuvre serait celui proposé à droite. Remarquez le recours (implictement correct) à une instance de cumuler<int> pour recevoir le résultat de for_each(). Il aurait été possible de le remplacer par un transtypage à l'aide de static_cast<cumuler<int>> pour éviter la construction d'une variable suipplémentaire. |
|
Cette écriture donne de bons résultats en pratique, mais nous pouvons faire un peu mieux...
Plutôt que de passer par un opérateur de conversion, puis du transtypage ou des variables intermédiaires, nous allons implémenter des fonctions explicites de transtypage gérées à l'interne.
compose_fx_gx.h (version 01b) | |
Remarquez la forme que prendra cette approche :
Cette forme est de loin meilleure qu'une autre qui reposerait sur des méthodes non génériques, par exemple to_fx() et to_gx(), car elle permet à un algorithme générique de relayer les demandes de conversion à un composite, comme dans l'exemple ci-dessous :
Cet exemple, en soi banal, n'en est qu'un parmi plusieurs possibles. |
|
Un exemple de programme reposant sur cette manoeuvre serait celui proposé à droite. Ici, la valeur retournée par for_each() est un composite dont la partie cumuler<int> est extraite volontairement et dont la valeur est par la suite obtenue. Tout ici se fait par copie, mais le coût d'une copie est minime (apparenté à celui de la copie des primitifs) pour tous les objets utilisés. Le code généré résultant sera, dans bien des cas, plus rapide que du code produit manuellement. |
|
La raison de cette dernière affirmation est que le composite créé, s'il est fait de foncteurs dont la copie est faite à coût négligeable, est en fait une expression (arithmétique ou autre) complexe applicable à un élément d'une séquence. Ceci ramène à une seule boucle l'application d'une expression complexe, plutôt qu'à plusieurs boucles l'application d'expressions simples. L'économie d'échelle, en temps et en espace, peut être considérable, et s'apparente à celle obtenue par l'optimisation très importante qu'on peut tirer du recours à des expression templates.
La prochaine étape, celle qui nous permettra d'atteindre un seuil de convivialité, de souplesse et de performance vraiment intéressant, sera de permettre d'exprimer aussi simplement des composites pouvant être faits à la fois d'expressions simples et d'autres composites, puis de convertir efficacement (à l'aide de la syntaxe to_<T>() ci-dessus) le composite le plus englobant en n'importe lequel de ses éléments constitutifs.