Introduction : que sont les generics en C++ et pourquoi en a-t-on besoin
Dans le monde de la programmation moderne, la réutilisation du code est l'un des principes clés. Si vous écrivez une fonction de sommation pour int, puis pour double et float, tôt ou tard vous voudrez éviter la duplication. C'est là que les generics (programmation générique) viennent à la rescousse. En C++, ce concept est implémenté via les modèles (templates).
Les generics permettent d'écrire du code qui fonctionne avec n'importe quel type de données, sans sacrifier les performances (contrairement aux fonctions virtuelles ou à void*). Le compilateur génère une version distincte de la fonction ou de la classe pour chaque type spécifique, ce qui offre une vitesse d'exécution maximale.
Dans cet article, nous verrons pourquoi les generics sont nécessaires en C++, comment les déclarer et les utiliser correctement en pratique, ainsi que les erreurs typiques et les techniques avancées.
Bases de la syntaxe : comment déclarer et utiliser un modèle
Un modèle en C++ se déclare avec le mot-clé template et des chevrons <>, à l'intérieur desquels on spécifie le paramètre de type (généralement typename ou class).
Fonction modèle
L'exemple le plus simple est une fonction qui retourne le maximum de deux valeurs :
template <typename T>T max(T a, T b) { return (a > b) ? a : b;}
// Utilisation :int main() { std::cout << max(3, 7) << std::endl; // 7 std::cout << max(3.14, 2.71) << std::endl; // 3.14 std::cout << max('a', 'z') << std::endl; // z return 0;}Remarque : le compilateur déduit automatiquement le type T en fonction des arguments passés. Si vous devez spécifier explicitement le type, vous pouvez écrire max<double>(3.14, 2.71).
Classe modèle
Les modèles fonctionnent également avec les classes. Un exemple classique est un conteneur simple « paire » :
template <typename T1, typename T2>class Pair {private: T1 first; T2 second;public: Pair(T1 f, T2 s) : first(f), second(s) {} T1 getFirst() const { return first; } T2 getSecond() const { return second; }};
// Utilisation :Pair<int, std::string> student(1, "Alice");std::cout << student.getFirst() << " " << student.getSecond();Ici, T1 et T2 sont des paramètres de modèle qui peuvent être de n'importe quel type.
Exemples pratiques : des conteneurs simples aux algorithmes
Examinons plusieurs scénarios réels où les generics sont indispensables.
Tri universel
Écrivons une fonction modèle de tri de tableau pour tout type supportant l'opérateur < :
template <typename T>void bubbleSort(T arr[], int n) { for (int i = 0; i < n - 1; ++i) for (int j = 0; j < n - i - 1; ++j) if (arr[j] > arr[j + 1]) std::swap(arr[j], arr[j + 1]);}
// Exemple :int intArr[] = {5, 2, 8, 1};bubbleSort(intArr, 4); // Fonctionne avec int
double dblArr[] = {3.14, 2.71, 1.41};bubbleSort(dblArr, 3); // Fonctionne avec doublePile avec type de données arbitraire
Implémentons une pile simple basée sur un modèle :
template <typename T>class Stack {private: std::vector<T> elements;public: void push(const T& value) { elements.push_back(value); } void pop() { if (!elements.empty()) elements.pop_back(); } T top() const { return elements.back(); } bool empty() const { return elements.empty(); }};
// Utilisation :Stack<int> intStack;intStack.push(10);intStack.push(20);std::cout << intStack.top(); // 20Algorithme « trouver et remplacer »
Fonction modèle qui remplace toutes les occurrences d'une valeur par une autre dans un conteneur :
templa