Notes de lecture "C++ Advanced Programming" (douze : utilisation de modèles pour écrire du code générique)

1. Références

2. Il est recommandé de lire le livre "21 Days to Learn C++" pour commencer. Le lien des notes est le suivant

1. Présentation du modèle

  • Les modèles poussent le concept de paramétrage un peu plus loin, permettant le paramétrage non seulement des valeurs, mais aussi des types. Les types en C++ incluent non seulement des types primitifs, tels que int et double, mais également des classes définies par l'utilisateur, telles que SpreadsheetCell et CherryTree. En utilisant des modèles, non seulement vous pouvez écrire du code qui ne dépend pas de valeurs spécifiques, mais vous pouvez également écrire du code qui ne dépend pas des types de ces valeurs

2. Modèles de classe

  • Un modèle de classe définit une classe dans laquelle les types de certaines variables, le type de retour d'une méthode et/ou les types de paramètres d'une méthode sont spécifiés en tant que paramètres. Les modèles de classe sont principalement utilisés pour les conteneurs ou les structures de données utilisées pour contenir des objets

2.1 Écrire des modèles de classe

  • Supposons que vous souhaitiez une classe de plateau générique que vous pouvez utiliser comme échiquier, plateau de dames, plateau de tic-tac-toe ou tout autre plateau en deux dimensions. Pour que ce plateau soit polyvalent, le plateau doit pouvoir contenir des pièces d'échecs, des pièces de dames, des pièces de tic tac toe ou tout autre type de jeu.
2.1.1 Définition de la classe de grille
  • La première ligne indique que la définition de classe ci-dessous est basée sur un type de modèle. Tout comme dans une fonction, le nom du paramètre est utilisé pour indiquer le paramètre à transmettre par l'appelant, et le nom du paramètre de modèle (tel que T) est utilisé dans le modèle pour indiquer le type à spécifier par l'appelant.
  • Lors de la spécification des paramètres de type de modèle, le mot-clé class peut être utilisé à la place de typename, mais class peut provoquer des malentendus, car le mot implique que le type doit être une classe , tandis que le type réel peut être class, struct, union, types primitifs tels comme int ou double etc.
    template <typename T>
    class Grid {
          
          
        // ...
    };
    
2.1.2 Définition de la méthode de la classe Grid
  • Le spécificateur d'accès du modèle <typename T> doit précéder chaque définition de méthode dans le modèle de grille
  • Le modèle nécessite que l'implémentation de la méthode soit également placée dans le fichier d'en-tête, car le compilateur doit connaître la définition complète, y compris la définition de la méthode, avant de créer une instance du modèle
    template <typename T>
    Grid<T>::Grid(size_t width, size_t height) : mWidth(width), mHeight(height) {
          
           // 构造函数
        // ...
    }
    
2.1.3 Utilisation du modèle Grille
  • Lors de la création d'un objet grille, vous ne pouvez pas utiliser Grid seul comme type et vous devez spécifier le type d'élément que cette grille enregistre . Le processus de création d'un objet de classe modèle pour un certain type est appelé instanciation de modèle. Voici un exemple

    Grid<int> myIntGrid;
    Grid<double> myDoubleGrid(11, 11);
    
    myIntGrid.at(0, 0) = 10;
    // at() 方法返回 std:optional 引用。optional 可包含值,也可不包含值
    // 如果 optional 包含值,value_or() 方法返回这个值;否则返回给 value_or() 提供的实参
    int x = myIntGrid.at(0, 0).value_or(0);
    
    Grid<int> grid2(myIntGrid); // 复制构造函数
    Grid<int> anotherIntGrid;
    anotherIntGrid = grid2; // 赋值运算符
    
  • Si vous souhaitez déclarer une fonction ou une méthode qui reçoit un objet Grid, vous devez spécifier le type d'élément stocké dans la grille dans le Type de grille

    void processIntGrid(Grid<int>& grid) {
          
          
        // ...
    }
    
  • Pour éviter d'avoir à écrire le nom complet du type Grid à chaque fois, par exemple Grid<int>, un nom plus simple peut être spécifié via un alias de type

    using IntGrid = Grid<int>;
    
  • Le type de données que le modèle de grille peut enregistrer n'est pas seulement int. Par exemple, vous pouvez instancier une grille contenant un SpreadsheetCell

    Grid<SpreadsheetCell> mySpreadsheet;
    SpreadsheetCell myCell(1.234);
    mySpreadsheet.at(3, 4) = myCell;
    
  • Les modèles de grille peuvent également contenir des types de pointeur

    Grid<const char*> myStringGrid;
    myStringGrid.at(2, 2) = "hello";
    
  • Le type spécifié par le modèle de grille peut même être un autre type de modèle

    Grid<vector<int>> gridOfVectors;
    vector<int> myVector{
          
          1, 2, 3, 4};
    gridOfVectors.at(5, 6) = myVector;
    
  • Le modèle de grille peut également allouer dynamiquement des instances de modèle de grille sur le tas

    auto myGridOnHeap = make_unique<Grid<int>>(2, 2);
    myGridOnHeap->at(0, 0) = 10;
    int x = myGridOnHeap->at(0, 0).value_or(0);
    

2.2 Principes des modèles de traitement du compilateur

  • Lorsque le compilateur rencontre une définition de méthode de modèle, il effectue une vérification de la syntaxe mais ne compile pas le modèle. Le compilateur ne peut pas compiler la définition du modèle car il ne sait pas quel type utiliser
  • Lorsque le compilateur rencontre un modèle instancié, tel que Grid<int> myIntGrid, il remplace chaque T dans la définition de classe de modèle par un int, générant ainsi le code pour la version int du modèle Grid. Lorsque le compilateur rencontre une autre instance de ce modèle, il génère une autre version de la classe Grid

2.3 Répartir le code du modèle sur plusieurs fichiers

  • En règle générale, vous placez les définitions de classe dans un fichier d'en-tête et les définitions de méthode dans un fichier de code source. Le code qui crée ou utilise un objet de classe inclura le fichier d'en-tête correspondant via #include et accédera à ces codes de méthode via l'éditeur de liens
  • Les modèles ne fonctionnent pas de cette façon . Étant donné que le compilateur doit utiliser ces "modèles" pour générer le code de méthode réel pour le type instancié, dans tout fichier de code source qui utilise des modèles, le compilateur doit avoir accès à la fois à la définition de classe de modèle et à la définition de méthode . Il existe plusieurs mécanismes pour répondre à cette exigence de confinement
2.3.1 Placer la définition du modèle dans le fichier d'en-tête
  • Les définitions de méthode peuvent être placées directement dans le même fichier d'en-tête que la définition de classe. Lorsqu'un fichier source qui utilise ce modèle inclut ce fichier via #include, le compilateur a accès à tout le code dont il a besoin. Alternativement, vous pouvez placer la définition de méthode de modèle dans un autre fichier d'en-tête, puis inclure ce fichier d'en-tête via #include dans le fichier d'en-tête de la définition de classe
    // Grid.h
    template <typename T>
    class Grid {
          
          
        // ...
    };
    // 一定要保证方法定义的 #include 在类定义之后,否则代码无法编译
    #include "GridDefinition.h"
    
2.3.2 Placer la définition du modèle dans le fichier source
  • Les définitions de méthode peuvent être placées dans un fichier de code source. Cependant, le code qui utilise le modèle doit toujours pouvoir accéder à la définition, de sorte que le fichier source pour l'implémentation de la méthode de classe peut être inclus dans le fichier d'en-tête de définition de classe du modèle par #include
    // Grid.h
    template <typename T>
    class Grid {
          
          
        // ...
    };
    
    #include "Grid.cpp"
    

2.4 Paramètres du modèle

2.4.1 Paramètres de modèle non typés
  • Les paramètres de modèle non typés ne peuvent être que des types entiers (char, int, long, etc.), des types d'énumération, des pointeurs, des références et std::nullptrt. À partir de C++17, vous pouvez également spécifier auto, auto& et auto* comme type de paramètres de modèle non typés. À ce stade, le compilateur déduira automatiquement le type
    template <typename T, size_t WIDTH, size_t HEIGHT>
    class Grid {
          
          
        // ...
    };
    // 实例化模板
    Grid<int, 10, 10> myGrid;
    Grid<int, 10, 10> anotherGrid;
    myGrid.at(2, 3) = 42;
    anotherGrid = myGrid;
    cout << anotherGrid.at(2, 3).value_or(0);
    
2.4.2 Valeurs par défaut des paramètres de type
  • Si vous continuez à utiliser la hauteur et la largeur comme paramètres de modèle, vous devrez peut-être fournir des valeurs par défaut pour la hauteur et la largeur (qui sont des paramètres de modèle non typés)
    template <typename T = int, size_t WIDTH = 10, size_t HEIGHT = 10>
    class Grid {
          
          
        // ...
    };
    
    // 不需要在方法定义的模板规范中指定 T、WIDTH 和 HEIGHT 的默认值
    template <typename T, size_t WIDTH, size_t HEIGHT>
    const std::optional<T>& Grid<T, WIDTH, HEIGHT>::at(size_t x, size_t y) const {
          
          
        // ...
    }
    
    // 实例化 Grid 时,可不指定模板参数,只指定元素类型,或者指定元素类型和宽度,或者指定元素类型、宽度和高度
    Grid<> myGrid;
    Grid<int> myGrid2;
    Grid<int, 5> myGrid3;
    Grid<int, 5, 5> myGrid4;
    
2.4.3 Déduction des paramètres de template pour les constructeurs
  • C++17 a ajouté des fonctionnalités pour prendre en charge la déduction automatique des arguments de modèle à partir des arguments passés aux constructeurs de modèles de classe . Avant C++17, tous les paramètres de modèle devaient être spécifiés explicitement pour les modèles de classe
  • Par exemple, la bibliothèque standard a un modèle de classe std:pair (défini dans <utility>). pair stocke deux valeurs de deux types différents, qui doivent être spécifiés comme paramètres de modèle
    std::pair<int, double> pair1(1, 2.3);
    
  • Pour éviter d'avoir à écrire des paramètres de modèle, un modèle de fonction d'assistance std:make_pair() est disponible. Les modèles de fonction ont toujours pris en charge la déduction automatique des arguments de modèle en fonction des arguments transmis au modèle de fonction. Par conséquent, make_pair() peut automatiquement déduire le paramètre de type de modèle de la valeur qui lui est transmise
    auto pair2 = std::make_pair(1, 2.3);
    
  • En C++17, de tels modèles de fonctions auxiliaires ne sont plus nécessaires et le compilateur peut déduire automatiquement les paramètres de type de modèle en fonction des paramètres réels passés au constructeur.
    std::pair pair3(1, 2.3);
    

    La prémisse de la déduction est que tous les paramètres de modèle du modèle de classe ont des valeurs par défaut ou sont utilisés comme paramètres dans le constructeur.
    std :: unique_ptr et shared_ptr désactiveront la déduction de type et devront continuer à utiliser make_unique () et make_shared ( ) créer

2.5 Modèles de méthode

  • C++ autorise les méthodes individuelles dans les classes basées sur des modèles, soit dans des modèles de classe, soit dans des classes non basées sur des modèles

    Impossible d'écrire des méthodes virtuelles et des destructeurs à l'aide de modèles de méthode

  • Un objet de type Grid<int> ne peut pas être assigné à un objet de type Grid<double>, et un Grid<double> ne peut pas non plus être construit à partir d'un Grid<int>

  • Le constructeur de copie de grille et l'opérateur = prennent tous deux une référence à un const <Grid> en tant que paramètre

    Grid(const Grid<T>& src);
    Grid<T>& operator=(const Grid<T>& rhs);
    
  • Lors de l'instanciation de Grid<double> et de la tentative d'appel du constructeur de copie et de l'opérateur =, le compilateur génère des méthodes à partir de ces prototypes

    Grid(const Grid<double>& src);
    Grid<double>& operator=(const Grid<double>& rhs);
    
  • Dans la classe Grid<double> générée, ni le constructeur ni l'opérateur= n'accepte Grid<int> comme paramètre, mais il peut être résolu par un double modèle : l'ajout d'un constructeur de copie et d'un opérateur d'affectation à la classe Grid peut générer des méthodes de conversion à partir de Un type de maillage à un autre

    template <typename T>
    class Grid {
          
          
    public:
        // ...
        template <typename E>
        Grid(const Grid<E>& src);
    
        template <typename E>
        Grid<T>& operator=(const Grid<E>& rhs);
    };
    
    • Voici la définition du nouveau constructeur de copie. La ligne déclarant le modèle de classe (avec le paramètre T) doit être placée avant la ligne déclarant le modèle de membre (avec le paramètre E)
    template <typename T>
    template <typename E>
    Grid<T>::Grid(const Grid<E>& src) : Grid(src.getWidth(), src.getHeight()) {
          
          
        // ...
    }
    

2.6 Spécialisation des modèles de classe

  • Une autre implémentation de modèles est appelée spécialisation de modèle, grâce à laquelle une implémentation spéciale peut être écrite pour un modèle lorsque le type de modèle est remplacé par un type spécifique.
  • Lors de l'écriture d'une spécialisation de classe de modèle, vous devez indiquer qu'il s'agit d'un modèle et le type spécifique pour lequel le modèle est écrit. Voici la spécialisation pour const char*
    // 下述语法告诉编译器,这个类是 Grid 类的 const char* 特例化版本
    template <>
    class Grid<const char*> {
          
          
        // ...
    };
    
    Grid<int> myIntGrid;
    Grid<const char*> stringGrid(2, 2);
    

    Notez que dans cette spécialisation, ne spécifiez aucune variable de type, telle que T, mais gérez directement const char*

  • Le principal avantage de la spécialisation est qu'elle peut être cachée à l'utilisateur : lorsque l'utilisateur crée une grille de type int ou SpreadsheetCell, le compilateur génère du code à partir du modèle de grille d'origine ; lorsque l'utilisateur crée une grille de type const char*, le le compilateur utilise un cas particulier de const char* Version optimisée, tout cela est fait automatiquement en arrière-plan
  • Lorsque vous spécialisez un template, vous n'héritez d'aucun code : la spécialisation, contrairement à la dérivation, doit réécrire toute l'implémentation de la classe, et n'est pas obligée de fournir des méthodes avec le même nom ou comportement
    • Par exemple, le cas spécial const char* de Grid implémente uniquement la méthode at(), renvoyant std::optional<std::string> au lieu de std::optional<const char*>

2.7 Dérivation d'un modèle de classe

  • Dérivable d'un modèle de classe. Si une classe dérivée hérite du modèle lui-même, la classe dérivée doit également être un modèle . En outre, une instance spécifique peut être dérivée d'un modèle de classe, auquel cas la classe dérivée n'a pas besoin d'être un modèle
  • Disons que la classe Grid générique ne fournit pas suffisamment de fonctionnalités de carte. Plus précisément, la méthode move () est ajoutée au plateau, permettant aux pièces du plateau d'être déplacées d'une position à une autre. Vous trouverez ci-dessous la définition de classe pour ce modèle GameBoard
    • Ce modèle GameBoard est dérivé du modèle Grid, il hérite donc de toutes les fonctions du modèle Grid
    • La syntaxe de l'héritage est la même que celle de l'héritage ordinaire, la différence est que la classe de base est Grid<T> au lieu de Grid
    #include "Grid.h"
    
    template <typename T>
    class GameBoard : public Grid<T> {
          
          
    public:
        explicit GameBoard(size_t width = Grid<T>::kDefaultWidth,
                           size_t height = Grid<T>::kDefaultHeight);
        void move(size_t xSrc, size_t ySrc, size_t xDest, size_t yDest);
    };
    

2.8 Héritage ou spécialisation

  • Étendre l'implémentation et utiliser le polymorphisme par héritage, et personnaliser l'implémentation de types spécifiques grâce à la spécialisation
    insérez la description de l'image ici

3. Modèles de fonctions

  • Des modèles peuvent également être écrits pour des fonctions individuelles. Par exemple, vous pouvez écrire une fonction générique qui recherche une valeur dans un tableau et renvoie l'index de cette valeur
    // size_t 是一个无符号整数类型
    // 通过这样的转换,可以将负值转换为等效的正值,以便在使用无符号整数时表示特殊的未找到或无效状态
    static const size_t NOT_FOUND = static_cast<size_t>(-1);
    
    template <typename T>
    // 这个 Find() 函数可用于任何类型的数组
    size_t Find(const T& value, const T* arr, size_t size) {
          
          
        for (size_t i = 0; i < size; ++i) {
          
          
            if (arr[i] == value) {
          
          
                return i;
            }
        }
        return NOT_FOUND;
    }
    
  • Comme les définitions de méthode de modèle de classe, les définitions de modèle de fonction (pas seulement les prototypes) doivent être disponibles dans tous les fichiers source où elles sont utilisées. Par conséquent, si plusieurs fichiers source utilisent un modèle de fonction ou utilisent une instanciation explicite comme indiqué précédemment, placez sa définition dans le fichier d'en-tête
  • Les paramètres de modèle des modèles de fonction peuvent avoir des valeurs par défaut, tout comme les modèles de classe

3.1 Spécialisation des modèles de fonction

template<>
size_t Find<const char*>(const char* const& value, const char* const* arr, size_t size) {
    
    
    for (size_t i = 0; i < size; ++i) {
    
    
        if (strcmp(arr[i], value) == 0) {
    
    
            return i;
        }
    }
    return NOT_FOUND;
}
const char* word = "two";
const char* words[] = {
    
    "one", "two", "three", "four"};
const size_t sizeWords = std::size(words);
size_t res;

res = Find<const char*>(word, words, sizeWords);
res = Find(word, words, sizeWords);

3.2 Plus d'introduction à la déduction d'argument de modèle

  • Le compilateur déduit le type du paramètre de modèle en fonction du paramètre réel passé au modèle de fonction, et pour le paramètre de modèle qui ne peut pas être déduit, il doit être explicitement spécifié . Par exemple, le modèle de fonction add() suivant nécessite trois paramètres de modèle : le type de la valeur de retour et les types des deux opérandes
    template <typename RetType, typename T1, typename T2>
    RetType add(const T1& t1, const T2& t2) {
          
          
        return t1 + t2;
    }
    
  • Lors de l'appel de ce modèle de fonction, vous pouvez spécifier les trois paramètres comme suit
    auto result = add<long long, int, int>(1, 2);
    
  • Mais comme les paramètres de modèle T1 et T2 sont des paramètres de la fonction, le compilateur peut déduire ces deux paramètres, donc lors de l'appel à add(), vous ne pouvez spécifier que le type de la valeur de retour
    auto result = add<long long>(1, 2);
    
  • Vous pouvez également fournir une valeur par défaut pour le paramètre de modèle de type de retour, afin que vous puissiez appeler add() sans spécifier de type
    template <typename RetType = long long, typename T1, typename T2>
    RetType add(const T1& t1, const T2& t2) {
          
          
        return t1 + t2;
    }
        
    auto result = add(1, 2);
    

3.3 Types de retour des modèles de fonction

  • Ne serait-il pas préférable de laisser le compilateur déduire le type de la valeur de retour ? C'est effectivement bien, mais le type de retour dépend du paramètre type de template, à partir de C++14, vous pouvez demander au compilateur de déduire automatiquement le type de retour de la fonction
    template <typename T1, typename T2>
    auto add(const T1& t1, const T2& t2) {
          
          
        return t1 + t2;
    }
    
  • Cependant, lorsque vous utilisez auto pour déduire le type d'expression, les qualificateurs reference et const sont supprimés.Après C++14, la fonction add() peut être écrite en utilisant decltype(auto) pour éviter de supprimer les qualificateurs const et reference
    template <typename T1, typename T2>
    decltype(auto) add(const T1& t1, const T2& t2) {
          
          
        return t1 + t2;
    }
    

4. Modèles variables

  • En plus des modèles de classe, des modèles de méthode de classe et des modèles de fonction , C++14 ajoute la possibilité d'écrire des modèles de variable
    template <typename T>
    constexpr T pi = T(3.14159265);
    
  • Ce qui précède est un modèle de variable pour la valeur pi. Afin d'obtenir la valeur de pi dans un certain type, la syntaxe suivante peut être utilisée
    float piFloat = pi<float>;
    long double piLongDouble = pi<long double>;
    
  • Cela se traduira toujours par une approximation de pi qui est représentable dans le type demandé. Comme les autres types de modèles, les modèles variadiques peuvent également être spécialisés

Je suppose que tu aimes

Origine blog.csdn.net/qq_42994487/article/details/131433144
conseillé
Classement