Generics en C++ : pourquoi sont-ils nécessaires et comment les utiliser
\n\nTout programmeur rencontre tôt ou tard une situation où il faut écrire une fonction qui fonctionne avec différents types de données. Par exemple, une fonction de recherche de l'élément maximum — elle doit fonctionner aussi bien avec int, double et string. Sans outils spéciaux, il faudrait écrire une implémentation séparée pour chaque type. C'est là que viennent à la rescousse les generics (types génériques) ou, comme on les appelle en C++, les modèles.
En C++, le mécanisme des modèles est la base de la programmation générique. Il permet d'écrire du code qui fonctionne avec n'importe quel type de données, sans sacrifier les performances. Dans cet article, nous allons voir ce que sont les generics en C++, pourquoi ils sont nécessaires et comment les utiliser en pratique. Le matériel est destiné aux débutants, nous allons donc progresser du simple au complexe.
\n\nQue sont les modèles (generics) en C++ ?
\n\nUn modèle (template) est une construction qui permet de définir une fonction ou une classe fonctionnant avec un type de données arbitraire. Au lieu d'un type concret, vous spécifiez un paramètre de type (par exemple, T), et le compilateur, lors de la compilation, substitue le type nécessaire. Cela s'appelle l'instanciation du modèle.
Imaginez un moule pour faire des biscuits. Le moule est unique, mais les biscuits peuvent être faits avec différentes pâtes : sablée, au chocolat, à l'avoine. Le modèle est le moule, et le type (int, double, string) est la pâte. Vous décrivez l'algorithme une fois, et le compilateur crée automatiquement des versions pour les types nécessaires.
\n\nIl existe deux types de modèles :
\n- \n
- Modèles de fonctions — pour créer des fonctions génériques. \n
- Modèles de classes — pour créer des classes génériques (par exemple, des conteneurs). \n
Modèles de fonctions : premier pas vers le code générique
\n\nCommençons par le plus simple — une fonction générique. Supposons que nous ayons besoin d'une fonction qui retourne le maximum de deux valeurs. Voici à quoi elle ressemble sans modèles :
\n\nint max_int(int a, int b) {\n return (a > b) ? a : b;\n}\n\ndouble max_double(double a, double b) {\n return (a > b) ? a : b;\n}\n\nLe code est dupliqué. Avec un modèle, cela se résout élégamment :
\n\n#include <iostream>\n#include <string>\n\ntemplate <typename T>\nT max_value(T a, T b) {\n return (a > b) ? a : b;\n}\n\nint main() {\n std::cout << max_value(10, 20) << std::endl; // 20\n std::cout << max_value(3.14, 2.71) << std::endl; // 3.14\n std::cout << max_value(std::string("apple"), \n std::string("banana")) << std::endl; // banana\n return 0;\n}\n\nLe mot-clé template indique au compilateur qu'il s'agit d'un modèle. <typename T> est la liste des paramètres du modèle. T est un nom de type conditionnel qui sera substitué plus tard. Au lieu de typename, on peut utiliser class — il n'y a pas de différence :
template <class T>\nT max_value(T a, T b) {\n return (a > b) ? a : b;\n}\n\nLorsque vous appelez max_value(10, 20), le compilateur voit que les arguments sont int et crée automatiquement une version de la fonction pour int. Ce processus s'appelle la déduction de types. Si nécessaire, vous pouvez spécifier explicitement le type :
std::cout << max_value<double>(5, 7.5) << std::endl; // 7.5\n\nImportant : une fonction générique doit être définie dans un fichier d'en-tête (ou avant son utilisation), car le compilateur doit voir la définition complète pour l'instanciation.
\n\nModèles de classes : créer des conteneurs universels
\n\nLes modèles de classes sont un outil encore plus puissant. L'exemple le plus frappant est celui des conteneurs de la bibliothèque standard (STL) : std::vector<int>, st