Notions de base sur les modèles C ++: modèles de fonctions

Avertissement: l' article a été commencé en février, mais, pour des raisons qui dépendent de moi, il n'était pas terminé. Le sujet est très vaste, il est donc publié sous une forme tronquée. Ce qui ne convenait pas sera examiné plus tard.



Il est impossible de comprendre le C ++ moderne sans savoir quels sont les modèles de programmation. Cette propriété du langage ouvre de larges possibilités d'optimisation et de réutilisation du code. Dans cet article, nous allons essayer de comprendre ce que c'est et comment tout cela fonctionne.

Le mécanisme des modèles en C ++ nous permet de résoudre le problème d'unification de l'algorithme pour différents types: il n'est pas nécessaire d'écrire différentes fonctions pour les types entier, réel ou utilisateur - il suffit de compiler un algorithme généralisé qui ne dépend pas du type de données, basé uniquement sur des propriétés communes. Par exemple, l'algorithme de tri peut fonctionner aussi bien avec des entiers qu'avec des objets de type «voiture».

Il existe des modèles de fonction et des modèles de classe.

Les modèles de fonction sont une description générale du comportement des fonctions qui peuvent être appelées pour des objets de différents types. En d'autres termes, un modèle de fonction (fonction de modèle, fonction généralisée) est une famille de fonctions différentes (ou une description d'algorithme). Par description, le modèle de fonction est similaire à une fonction régulière: la différence est que certains éléments ne sont pas définis (types, constantes) et sont paramétrés.

Modèles de classe - une description généralisée d'un type d'utilisateur dans laquelle les attributs et les opérations de type peuvent être paramétrés. Ce sont des constructions par lesquelles des classes réelles peuvent être générées en substituant des arguments concrets au lieu de paramètres.

Examinons plus en détail les modèles de fonction.

Modèles de fonctions


Comment écrire la première fonction de modèle?


Considérons le cas de la détermination de l'élément minimum de deux. Dans le cas d'entiers et de nombres réels, vous devrez écrire 2 fonctions.

int _min(int a, int b){ if( a < b){ return a; } return b; } double _min(double a, double b){ if( a < b){ return a; } return b; } 

Vous pouvez, bien sûr, implémenter une seule fonction, avec des paramètres valides, mais pour comprendre les modèles, cela sera nocif.
Que se passe-t-il si l'application est compilée? Les deux implémentations de la fonction tomberont dans le code binaire de l'application, même si elles ne sont pas utilisées (cependant, maintenant les compilateurs sont très intelligents, ils peuvent couper le code inutilisé). Et si vous avez besoin d'ajouter une fonction qui définit le minimum de 2 lignes (c'est difficile à imaginer sans préciser qu'il y a une ligne minimum)?!

Dans ce cas, si l'algorithme est commun aux types avec lesquels vous devez travailler, vous pouvez définir un modèle de fonction. Le principe, dans le cas général, sera le suivant:

  1. une implémentation de fonction est prise pour certains types;
  2. le modèle d'en-tête <Type de classe> (ou le modèle <Type de nom de type>) est attribué, ce qui signifie que l'algorithme utilise une sorte de Type abstrait;
  3. dans l'implémentation de la fonction, le nom du type est remplacé par Type.

Pour la fonction min, nous obtenons ce qui suit:

 template<class Type> Type _min(Type a, Type b){ if( a < b){ return a; } return b; } 

Le plus intéressant est le fait que même s'il n'y a pas d'appel à la fonction min, une fois compilée, elle n'est pas créée en code binaire (non instanciée ). Et si vous déclarez un groupe d'appels de fonction avec des variables de différents types, pour chaque compilateur créera sa propre implémentation basée sur le modèle.

Appeler une fonction modèle équivaut généralement à appeler une fonction ordinaire. Dans ce cas, le compilateur déterminera le type à utiliser au lieu de Type, en fonction du type des paramètres réels. Mais si les paramètres substitués s'avèrent être de types différents, le compilateur ne pourra pas générer (instancier le modèle) l'implémentation du modèle. Ainsi, dans le code suivant, le compilateur trébuchera sur le troisième appel, car il ne peut pas déterminer quel type est (pensez pourquoi?):

 #include <iostream> template<class Type> Type _min(Type a, Type b) { if (a < b) { return a; } return b; } int main(int argc, char** argv) { std::cout << _min(1, 2) << std::endl; std::cout << _min(3.1, 1.2) << std::endl; std::cout << _min(5, 2.1) << std::endl; // oops! return 0; } 

Ce problème est résolu en spécifiant un type spécifique lors de l'appel de la fonction.

 #include <iostream> template<class Type> Type _min(Type a, Type b) { if (a < b) { return a; } return b; } int main(int argc, char** argv) { std::cout << _min<double>(5, 2.1) << std::endl; return 0; } 

Quand la fonction de modèle fonctionnera-t-elle (non)?


En principe, vous pouvez comprendre que le compilateur remplace simplement le type souhaité dans le modèle. Mais la fonction résultante sera-t-elle toujours fonctionnelle? Evidemment non. Tout algorithme peut être défini quel que soit le type de données, mais il utilise nécessairement les propriétés de ces données. Dans le cas de la fonction modèle _min, il s'agit d'une exigence pour définir un opérateur de commande (opérateur <).

Tout modèle de fonction suppose la présence de certaines propriétés d'un type paramétré, selon l'implémentation (par exemple, un opérateur de copie, un opérateur de comparaison, la présence d'une méthode spécifique, etc.). Dans la norme de langage C ++ attendue, les concepts en seront responsables.

Surcharge du modèle de fonction


Les modèles de fonction peuvent également être surchargés. Habituellement, cette surcharge est effectuée lorsque

 template<class Type> Type* _min(Type* a, Type* b){ if(*a < *b){ return a; } return b; } 

Cas particuliers


Dans certains cas, le modèle de fonction est inefficace ou incorrect pour un type particulier. Dans ce cas, vous pouvez spécialiser le modèle, c'est-à-dire écrire une implémentation pour ce type. Par exemple, dans le cas de chaînes, vous souhaiterez peut-être que la fonction compare uniquement le nombre de caractères. En cas de spécialisation du modèle de fonction, le type pour lequel le modèle est spécifié n'est pas spécifié dans le paramètre. Voici un exemple de cette spécialisation.

 template<> std::string _min(std::string a, std::string b){ if(a.size() < b.size()){ return a; } return b; } 

La spécialisation du modèle pour des types spécifiques se fait à nouveau pour des raisons d'économie: si cette version du modèle de fonction n'est pas utilisée dans le code, elle ne sera pas incluse dans le code binaire.
Pour l'avenir, les paramètres multiples et entiers restent. Une extension naturelle est les modèles de classe, les bases de la programmation générative et la conception de la bibliothèque standard C ++. Et un tas d'exemples!

Source: https://habr.com/ru/post/fr436880/


All Articles