Article de reference

Modèle (C++)

( Apprenez comment et quand supprimer ce message ) Les modèles (templates) sont une fonctionnalité du langage de programmation C++ qui permet aux fonctions et aux classes de man...

( Apprenez comment et quand supprimer ce message )

Les modèles (templates) sont une fonctionnalité du langage de programmation C++ qui permet aux fonctions et aux classes de manipuler des types génériques . Cela permet à une fonction ou à une classe de référencer , via une variable générique, une autre classe ( type de données intégré ou nouvellement déclaré ) sans avoir à créer une déclaration complète pour chacune de ces classes.

En clair, une classe ou une fonction paramétrée par modèle revient à copier-coller (avant compilation) le bloc de code paramétré à l'endroit où il est utilisé, puis à remplacer le paramètre paramétré par sa valeur réelle. C'est pourquoi les classes utilisant des méthodes paramétrées placent l'implémentation dans les fichiers d'en-tête ( *.h ) : aucun symbole ne peut être compilé sans connaître son type au préalable.

La bibliothèque standard C++ fournit de nombreuses fonctions utiles au sein d'un cadre de modèles interconnectés.

Les principaux modèles C++ s'inspirent des modules paramétrés fournis par le langage CLU et des génériques fournis par Ada .

Aperçu technique

Il existe trois types de modèles : les modèles de fonctions , les modèles de classes et, depuis C++14 , les modèles de variables . Depuis C++11 , les modèles peuvent être variadiques ou non variadiques ; dans les versions antérieures de C++, ils sont toujours non variadiques.

Les modèles C++ sont Turing-complets .

Modèles de fonctions

modèle < class Identifier > Déclaration ; modèle < typename Identifier > Déclaration ;

Les deux expressions ont la même signification et se comportent exactement de la même manière. La seconde forme a été introduite pour éviter toute confusion , car un paramètre de type n'a pas besoin d'être une classe avant C++20. (Il peut s'agir d'un type de base tel que ` intint` ou `string` double.)

Par exemple, la bibliothèque standard C++ contient le modèle de fonction max(x, y)qui renvoie la plus grande valeur de xet y. Ce modèle de fonction pourrait être défini comme ceci :

template < typename T > [[ nodiscard ]] constexpr T & max ( const T & a , const T & b ) noexcept { return a < b ? b : a ; }

Cette définition de fonction unique est compatible avec de nombreux types de données. Plus précisément, elle fonctionne avec tous les types de données pour lesquels l'opérateur < (inférieur à) est défini et renvoie une valeur dont le type est convertible bool. L'utilisation d'un modèle de fonction permet de gagner de la place dans le fichier source, de limiter les modifications à une seule description de fonction et d'améliorer la lisibilité du code.

Un modèle de fonction instancié produit généralement le même code objet que si l'on écrivait des fonctions distinctes pour chaque type de données utilisé dans un programme. Par exemple, si un programme utilise à la fois une version de `int` intet une doubleversion de `float` du max()modèle de fonction ci-dessus, le compilateur créera une version de code objet de ` int` qui max()gère intles arguments `int` et une autre version de code objet qui gère les doublearguments `float`. Le résultat du compilateur sera identique à celui obtenu si le code source avait contenu deux versions distinctes non modélisées de `int` , l'une gérant les arguments `int` et l'autre les arguments `float` .max()intdouble

Voici comment le modèle de fonction pourrait être utilisé :

import std ;int main () { // Ceci appellera max<int> par déduction implicite d'argument. std :: println ( "{}" , std :: max ( 3 , 7 ));// Ceci appellera max<double> par déduction implicite des arguments. std :: println ( "{}" , std :: max ( 3.0 , 7.0 ));// Nous devons spécifier explicitement le type des arguments ; // bien que std::type_identity puisse résoudre ce problème... std :: println ( "{}" , max <double> ( 3 , 7.0 ) ); }

Dans les deux premiers cas, l'argument de modèle Test automatiquement déduit par le compilateur comme étant respectivement intet double. Dans le troisième cas, la déduction automatique de max(3, 7.0)échouerait car le type des paramètres doit généralement correspondre exactement aux arguments de modèle. Par conséquent, nous instancions explicitement la doubleversion avec max<double>().

Ce modèle de fonction peut être instancié avec n'importe quel type constructible par copie pour lequel l'expression est valide. Pour les types définis par l'utilisateur, cela implique que l'opérateur inférieur à ( ) doit être surchargé dans le type.y<x<

Modèles de fonctions abrégées

Depuis C++20 , l'utilisation autode `or` concept autodans l'un des paramètres d'une déclaration de fonction transforme cette déclaration en une déclaration de modèle de fonction abrégée . Une telle déclaration définit un modèle de fonction et un paramètre de modèle inventé pour chaque espace réservé est ajouté à la liste des paramètres de modèle :

// équivalent à : // modèle <typename T> // void f1(T x); void f1 ( auto x );// équivalent à (si Concept1 est un concept) : // <Concept1 T> // void f2(T x); void f2 ( Concept1 auto x );// équivalent à (si Concept2 est un concept) : // modèle <Concept2... Ts> // void f3(Ts... xs) void f3 ( Concept2 auto ... xs );// équivalent à (si Concept2 est un concept) : // modèle <Concept2 T> // void f4(T... xs); void f4 ( Concept2 auto xs , ...);// équivalent à (si Concept3 et Concept4 sont des concepts) : // modèle <Concept3 T, Concept4 U> // f5(const T* t, U& u); void f5 ( const Concept3 auto * t , Concept4 auto & u );

La limitation des max()concepts utilisés pourrait ressembler à ceci :

en utilisant std :: totalement_ordonné ;// dans la déclaration du nom de type : template < totally_ordered T > [[ nodiscard ]] constexpr T max ( T x , T y ) noexcept { return x < y ? y : x ; }// dans la clause requires : template < typename T > requires totally_ordered < T > [[ nodiscard ]] constexpr T max ( T x , T y ) noexcept { return x < y ? y : x ; }

Modèles de classes

Un modèle de classe fournit une spécification pour la génération de classes à partir de paramètres. Les modèles de classe sont généralement utilisés pour implémenter des conteneurs . Un modèle de classe est instancié en lui passant un ensemble donné de types comme arguments de modèle. La bibliothèque standard C++ contient de nombreux modèles de classe, en particulier les conteneurs adaptés de la bibliothèque de modèles standard (STL) , tels que vector.

Modèles de variables

template < typename T > constexpr T PI = T { 3.141592653589793238462643383L }; // (Presque) de std::numbers::pi

Paramètres de modèle non typés

Bien que la modélisation par types, comme dans les exemples ci-dessus, soit la forme la plus courante de modélisation en C++, il est également possible de modéliser des valeurs. Ainsi, par exemple, une classe déclarée avec

modèle < int K > classe MaClasse ;

peut être instancié avec un spécifique int.

À titre d'exemple concret, le type de tableau de taille fixe de la bibliothèque standard est paramétré à la fois par un type (représentant le type d'objet contenu dans le tableau) et par un nombre de type `int` (représentant le nombre d'éléments contenus dans le tableau). Pour créer une classe équivalente , on peut la déclarer comme suit :std::arraystd::size_tArraystd::array

modèle < classe T , taille_t N > struct Tableau ;

et un tableau de six chars pourrait être déclaré :

Tableau < char , 6 > monTableau ;

Spécialisation par modèle

Lorsqu'une fonction ou une classe est instanciée à partir d'un modèle, une spécialisation de ce modèle est créée par le compilateur pour l'ensemble des arguments utilisés, et cette spécialisation est appelée spécialisation générée.

spécialisation explicite des modèles

Il arrive que le programmeur choisisse d'implémenter une version spécifique d'une fonction (ou d'une classe) pour un ensemble donné d'arguments de type modèle ; on parle alors de spécialisation explicite. Ainsi, certains types modèles peuvent bénéficier d'une implémentation spécialisée, optimisée pour le type lui-même ou plus pertinente que l'implémentation générique.

La spécialisation explicite est utilisée lorsque le comportement d'une fonction ou d'une classe, pour des choix particuliers des paramètres du modèle, doit différer du comportement générique : c'est-à-dire du code généré par le modèle principal, ou les modèles. Par exemple, la définition de modèle ci-dessous définit une implémentation spécifique pour max()les arguments de typeconst char* :

import std ;template <> [[ nodiscard ]] constexpr const char * max ( const char * a , const char * b ) noexcept { // Normalement, le résultat d'une comparaison directe // entre deux chaînes C est un comportement indéfini ; // l'utilisation de std::strcmp le rend défini. return std :: strcmp ( a , b ) > 0 ? a : b ; }

Modèles variadiques

C++11 a introduit les modèles variadiques , qui peuvent prendre un nombre variable d'arguments d'une manière quelque peu similaire aux fonctions variadiques telles que std::printf.

using std :: format_string ; using std :: ofstream ;classe énumérée Niveau { ... };ofstream logFile { "logfile.txt" };template < typename ... Args > void log ( const format_string < Args ... >& fmt , Args && ... args ) { std :: println ( logFile , fmt , args ...); }

Étant donné que C++ ne prend en charge que les paramètres variadiques de style C, la seule façon d'obtenir des fonctions variadiques de type sûr (comme en Java ) est d'utiliser des modèles variadiques.

Alias ​​de modèles

C++11 a introduit les alias de modèles, qui agissent comme des typedefs paramétrés .

Le code suivant illustre le renommage std::mapen TreeMapet std::unordered_mapen HashMap, ainsi que la création d'un alias StringHashMappour std::unordered_map<K, std::string>. Cela permet, par exemple, StringHashMap<int>d'utiliser comme raccourci pour std::unordered_map<int, std::string>.

utiliser String = std :: string ;// permettant la spécialisation optionnelle des fonctions de hachage, des allocateurs, etc. template < typename K , typename V , typename Compare = std :: less < K > , typename Alloc = std :: allocator < std :: pair < ​​const K , T >> > using TreeMap = std :: map < K , V , Compare , Alloc > ;modèle < typename K , typename V , typename HashFn = std :: hash < K > , typename KeyEq = std :: equal_to < K > , typename Alloc = std :: allocator < std :: pair < ​​const K , T >> > using HashMap = std :: unordered_map < K , V , HashFn , KeyEq , Alloc > ;// ou, en autorisant uniquement K et V à être spécialisés : template < typename K , typename V > using TreeMap = std :: map < K , V > ;modèle < typename K , typename V > utilisant HashMap = std :: unordered_map < K , V > ;// Définition de StringHashMap<K> = HashMap<K, String> template < typename K > using StringHashMap = HashMap < K , String > ;StringHashMap < int > myMap = /* quelque chose ici... */ ;

Modèles contraints

Depuis C++20 , les modèles peuvent être contraints de manière similaire aux caractères génériques en Java ou aux clauses en C# et Rustwhere . Ceci est réalisé à l'aide de concepts , qui représentent un ensemble de prédicats booléens évalués à la compilation.

Par exemple, ce code utilise `T` std::derived_from<Derived, Based>comme limite supérieure d'héritage. Une classe satisfait ce concept si elle hérite de `T` Player, et les classes qui n'héritent pas de `T` ne peuvent pas être utilisées comme paramètre de modèle dans `T` processListOfPlayers().

import std ;using std :: derived_from ; using std :: vector ;classe Joueur { // ... };// T doit être un type dont la limite supérieure d'héritage est Player, // bloquant tout type qui n'hérite pas de Player template < derived_from < Player > T > void processListOfPlayers ( const vector < T >& players ) { // ... }

Des concepts peuvent être utilisés pour implémenter une forme de paramètres variadiques typés. Par exemple, la signature Java<T>voidfn(T...args);

en utilisant std :: même_que ;template < typename T > void fn ( same_as < T > auto ... args ) { // ... }

De plus, pour contraindre ces éléments à un type spécifique, les concepts ` and` et `is` peuvent être utilisés. Par exemple, les signatures Java ` and` pourraient s'écrire respectivement `and` et `is` .std::same_as<T,U>std::convertible_to<T,U>voidfoo(int...args);voidbar(String...args);voidfoo(same_as<int>auto...args);voidbar(convertible_to<string>auto...args);

Modèles exportés

En C++03 , les « modèles exportés » ont été introduits. Ils ont ensuite été supprimés en C++11 , car très peu de compilateurs prenaient en charge cette fonctionnalité. Le seul compilateur connu pour prendre en charge les modèles exportés était Comeau C/C++ . Parmi les raisons invoquées pour leur suppression, on peut citer :

Un « modèle exporté » est essentiellement un modèle de classe dont les membres de données statiques et les méthodes non inline sont exportés. Il doit être marqué par le mot-clé export`exported`. Ce qui distingue un « modèle exporté » est le fait qu'il n'est pas nécessaire de le définir dans une unité de traduction qui l'utilise. Par exemple (en C++03) :

Fichier1.cpp :

#include <iostream>static void trace () { std :: cout << "Fichier 1" << std :: endl ; }export template < typename T > T min ( const T & x , const T & y );int main () { trace (); std :: cout << min ( 2 , 3 ) << std :: endl ; }

Fichier2.cpp :

#include <iostream>static void trace () { std :: cout << "Fichier 2" << std :: endl ; }export template < typename T > T min ( const T & x , const T & y ) { trace (); return a < b ? a : b ; }

Avec l'introduction des modules en C++20 , le mot-clé export`module` a été réintroduit. Cela a permis de nouveau d'autoriser des déclarations comme celle-ci :

import std ;en utilisant std :: est_base_de_v ;export class Atom { // ... };export template < typename T > concept ExtendsAtom = is_base_of_v < Atom , T > ;export template < ExtendsAtom Instance , typename ... Bases > class Cluster : public Bases ... { private : Instance x ; public : explicit Cluster ( Instance x , Bases && ... bases ) : Bases ( bases )..., x { x } {}// ... };

Les gains de vitesse de compilation que devaient offrir les modèles exportés sont de toute façon offerts par les modules, ce qui rend cette fonctionnalité obsolète et la fait remplacer par les modules.

Fonctionnalités de programmation génériques dans d'autres langues

Initialement, le concept de modèles n'était pas intégré à certains langages, comme Java et C# 1.0. L'adoption des génériques en Java imite le comportement des modèles, mais diffère techniquement. C# a introduit les génériques (types paramétrés) dans .NET 2.0. Les génériques en Ada sont antérieurs aux modèles C++.

Bien que les modèles C++, les génériques Java et les génériques .NET soient souvent considérés comme similaires, les génériques ne font qu'imiter le comportement de base des modèles // Modèle d'induction < unsigned int N > struct Factorial { static constexpr unsigned int value = N * Factorial < N - 1 >:: value ; };// Cas de base via spécialisation de modèle : template <> struct Factorial < 0 > { static constexpr unsigned int value = 1 ; };

Avec ces définitions, on peut calculer, par exemple, 6! au moment de la compilation en utilisant l'expression Factorial<6>::value.

Alternativement, constexpren C++11 / if constexpren C++17, il est possible de calculer directement ces valeurs à l'aide d'une fonction lors de la compilation :

template < unsigned int N > [[ nodiscard ]] constexpr unsigned int factorial () noexcept { if constexpr ( N ​​<= 1 ) { return 1 ; } else { return N * factorial < N - 1 > (); } }

C’est pourquoi la métaprogrammation par modèles est aujourd’hui principalement utilisée pour effectuer des opérations sur des types.

Plus d articles de Worldlex Wiki

Revenez a l index pour explorer davantage de pages sur l histoire, la science, la culture, la geographie et la societe en francais.

Explorer l index