Paramètres de modèle non-type
Habituellement, nous utilisons les templates pour passer des types en paramètres. Cependant, le C++ permet également d'utiliser des valeurs constantes comme paramètres de modèle : c'est ce qu'on appelle les paramètres de modèle non-type. Ces paramètres sont traités comme des constantes lors de la compilation.
#include <iostream>
template <typename T, int Capacite = 100>
class ConteneurStatique {
public:
void AfficherInfos() {
std::cout << "Taille du tableau statique : " << Capacite << std::endl;
}
private:
T _donnees[Capacite];
};
int main() {
// Instanciation avec une valeur entière comme paramètre
ConteneurStatique<int, 50> monTableau;
monTableau.AfficherInfos();
return 0;
}
Il est important de noter que ces paramètres doivent être des entiers (incluant bool, char, long, etc.). Les nombres à virgule flottante ou les chaînes de caractères ne sont traditionnellement pas autorisés comme paramètres non-type dans les versions antérieures du standard C++.
La spécialisation de modèles
La spécialisation intervient lorsqu'un modèle générique ne convient pas à un type spécifique. Par exemple, si vous comparez deux pointeurs à l'aide d'un template de comparaison standard, le compilateur comparera les adresses mémoires au lieu des valeurs pointées. La spécialisation permet de redéfinir le comoprtement pour ces cas précis.
#include <iostream>
class Produit {
public:
Produit(int id, double prix) : _id(id), _prix(prix) {}
bool operator>(const Produit& p) const {
return _prix > p._prix;
}
private:
int _id;
double _prix;
};
// Modèle de base
template<typename T>
struct Comparateur {
bool operator()(const T& gauche, const T& droite) const {
return gauche > droite;
}
};
// Spécialisation pour les pointeurs de Produit
template<>
struct Comparateur<Produit*> {
bool operator()(Produit* gauche, Produit* droite) const {
return *gauche > *droite;
}
};
int main() {
Produit* p1 = new Produit(1, 15.5);
Produit* p2 = new Produit(2, 25.0);
Comparateur<Produit*> comp;
// Utilise la spécialisation pour comparer les objets et non les adresses
std::cout << "Résultat : " << comp(p1, p2) << std::endl;
delete p1;
delete p2;
return 0;
}
Spécialisation Totale vs Partielle
On distingue deux types de spécialisation :
- Spécialisation totale : Tous les paramètres du template sont fixés.
- Spécialisation partielle : Une partie seulement des paramètres est fixée, ou les paramètres sont restreints (par exemple, accepter uniquement des pointeurs).
template<typename T1, typename T2>
class Generique {
public:
Generique() { std::cout << "Modèle standard" << std::endl; }
};
// Spécialisation totale
template<>
class Generique<int, double> {
public:
Generique() { std::cout << "Spécialisation totale (int, double)" << std::endl; }
};
// Spécialisation partielle
template<typename T>
class Generique<T, int> {
public:
Generique() { std::cout << "Spécialisation partielle (T, int)" << std::endl; }
};
Compilation séparée des templates
L'utilisation de fichiers .h et .cpp séparés pour les templates pose souvent problème. Lors de la compilation d'un fichier source qui utilise un template, le compilateur doit avoir accès à la définition complète du template pour générer le code correspondant au type spécifié. Si seule la déclaration est présente dans le header, le lieur (linker) générera une erreur car il ne trouvera pas le code instancié dans les fichiers objets.
Il existe deux solutions principales pour pallier ce problème :
- Instanciation explicite : Forcer le compilateur à générer le code pour des types précis dans le fichier
.cpp. - Inclusion de la définition : Placer la définition des méthodes directement dans le fichier d'en-tête (méthode recomandée et la plus utilisée).
// Solution recommandée : Définition dans le header
// fichier : Gestionnaire.hpp
template<typename T>
class Gestionnaire {
public:
void Executer() {
std::cout << "Exécution du traitement..." << std::endl;
}
};
Analyse des templates
Les templates sont un outil puissant mais comportent des compromis :
Points forts
- Réutilisabilité : Permet d'écrire une seule logique pour une multitude de types.
- Performance : Le code est généré à la compilation, évitant le surcoût de l'indirection à l'exécution.
- Sécurité du typage : Le contrôle est effectué lors de la compilation.
Points faibles
- Gonflement du binaire : Chaque instanciation génère une nouvelle copie du code, ce qui peut augmenter la taille du fichier exécutable.
- Temps de compilation : Plus il y a de templates et de spécialisations, plus la phase de compilation est longue.
- Complexité des erreurs : Les messages d'erreur liés aux templates sont souvent longs et difficiles à interpréter.