Que sont les foncteurs en C++ et pourquoi en a-t-on besoin ?
\n\nImaginez que vous écrivez du code en C++ et que vous devez passer un comportement (une fonction) à une autre fonction, par exemple pour trier ou filtrer des données. La solution classique est d'utiliser des pointeurs de fonction. Mais ils ont des inconvénients : ils ne stockent pas d'état, ils sont difficiles à intégrer (inline) et ils semblent un peu archaïques. C'est là qu'interviennent les foncteurs en C++ (également appelés objets-fonctions ou objets fonctionnels C++).
\n\nUn foncteur est une classe ou une structure ordinaire dans laquelle l'opérateur d'appel de fonction operator() est surchargé. Grâce à cela, une instance d'une telle classe peut être utilisée comme une fonction ordinaire. Cela semble compliqué ? En réalité, c'est l'un des outils les plus élégants du C++. Sur Stack Overflow, la question "What are C++ functors and their uses?" a recueilli des milliers de vues — les développeurs veulent comprendre ce sujet pour écrire un code plus flexible et plus performant. Dans ce guide, nous allons tout détailler : de la syntaxe de base aux exemples concrets d'utilisation avec la STL.
Syntaxe de base : comment créer votre premier foncteur
\n\nLa création d'un foncteur se résume à trois étapes :
\n- \n
- Déclarer une classe ou une structure. \n
- Implémenter à l'intérieur
operator()avec la signature souhaitée (paramètres et type de retour). \n - Créer un objet de cette classe et l'appeler comme une fonction. \n
Voici un exemple simple de foncteur C++ qui multiplie un nombre par deux :
\n\n#include <iostream>\n\nclass Multiplier {\npublic:\n // Surcharge de l'opérateur ()\n int operator()(int x) const {\n return x * 2;\n }\n};\n\nint main() {\n Multiplier mult; // Création d'un objet-fonction\n std::cout << mult(5) << std::endl; // Affiche : 10\n std::cout << mult(10) << std::endl; // Affiche : 20\n return 0;\n}\n\nNotez que mult(5) ressemble à un appel de fonction ordinaire, mais il s'agit en réalité d'un appel à mult.operator()(5). Le compilateur fait du sucre syntaxique, et le code devient propre et intuitif.
Avantage principal : les foncteurs peuvent stocker un état
\n\nContrairement aux fonctions ordinaires ou aux expressions lambda (avant C++11), les objets fonctions C++ peuvent avoir leurs propres champs membres. Cela ouvre d'énormes possibilités. Par exemple, vous pouvez créer un foncteur qui non seulement effectue une action, mais mémorise également combien de fois il a été appelé :
\n\n#include <iostream>\n\nclass Counter {\nprivate:\n int count = 0;\npublic:\n int operator()() {\n return ++count;\n }\n int getCount() const { return count; }\n};\n\nint main() {\n Counter counter;\n std::cout << counter() << std::endl; // 1\n std::cout << counter() << std::endl; // 2\n std::cout << counter() << std::endl; // 3\n // L'état est conservé entre les appels !\n return 0;\n}\n\nCet exemple de foncteur C++ avec état ne peut pas être réalisé avec une simple fonction sans utiliser de variables globales (ce qui est mauvais). Les foncteurs résolvent ce problème de manière élégante et sûre.
\n\nFoncteurs et algorithmes STL : une combinaison parfaite
\n\nL'utilisation la plus puissante des foncteurs est le travail avec les algorithmes de la Standard Template Library (STL). Les algorithmes comme std::sort, std::find_if, std::for_each attendent en entrée un objet appelable (callable). Vous pouvez leur passer un foncteur, et il exécutera la logique souhaitée.
Exemple : tri avec un foncteur
\n\nSupposons que vous ayez un vecteur de nombres et que vous souhaitiez les trier par ordre décroissant. Vous pouvez écrire votre propre foncteur de comparaison :
\n\n#include <iostream>\n#include <vector>\n#include <algorithm>\n\nclass Descending {\npublic:\n bool operator()(int a, int b) const {\n return a > b; // Retourne