Programmation • IoT

Chapitre 9 – Classe MatriceT

Document réservé

Vous consultez actuellement la présentation publique de ce chapitre.

Les documents PDF complets, comprenant les développements théoriques, les exemples détaillés et les exercices, sont disponibles sur demande.

Pour obtenir un accès, contactez-moi via la page Contact en indiquant les domaines qui vous intéressent (C++, ESP-IDF, électronique, etc.).

9.1 Pourquoi rendre Matrice générique ? . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.1.1 Le type des coefficients devient un paramètre . . . . . . . . . . . . . . . . . . 9.1.2 Faire évoluer progressivement la classe . . . . . . . . . . . . . . . . . . . . . . 9.2 Transformer la classe Matrice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.2.1 Quels éléments doivent changer ? . . . . . . . . . . . . . . . . . . . . . . . . . 9.2.2 Les fonctions qui manipulent un coefficient . . . . . . . . . . . . . . . . . . . 9.2.3 L’allocation dynamique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.3 Les définitions dans le fichier Matrice.tpp . . . . . . . . . . . . . . . . . . . . . . . . 9.3.2 Le constructeur de copie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.3.3 L’opérateur d’affectation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.4 Les opérations sur les coefficients . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.4.2 Modifier un coefficient . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.4.4 Transposer une matrice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.4.5 Une généralisation presque mécanique . . . . . . . . . . . . . . . . . . . . . . 9.5 La multiplication et le type T . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.5.1 Une hypothèse cachée dans l’algorithme . . . . . . . . . . . . . . . . . . . . . 9.5.3 L’initialisation par défaut . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.5.4 La multiplication générique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.5.5 Un template impose des exigences au type utilisé . . . . . . . . . . . . . . . . 9.6 Chaque instanciation constitue un type différent . . . . . . . . . . . . . . . . . . . . . 9.6.1 Deux classes concrètes différentes . . . . . . . . . . . . . . . . . . . . . . . . . 9.6.2 Une conséquence importante . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.7 Hériter d’une classe template . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.7.1 Une classe template dérivée d’une classe template . . . . . . . . . . . . . . . 9.7.2 Appeler le constructeur de la classe de base . . . . . . . . . . . . . . . . . . . 9.7.3 Accéder aux membres hérités . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.7.6 Templates et héritage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.8.1 D’une classe à une famille de classes . . . . . . . . . . . . . . . . . . . . . . . 9.8.2 Le code générique n’est pas nécessairement universel . . . . . . . . . . . . . . 9.8.3 Templates et programmation orientée objet . . . . . . . . . . . . . . . . . . . 9.8.4 Une synthèse de la programmation orientée objet . . . . . . . . . . . . . . . . 2026 – Partie II C++
Accueil
La classe Matrice nous a déjà accompagnés au cours de notre étude de la programmation orientée objet. Elle nous a notamment permis de mettre en pratique la construction et la destruction des objets, l’allocation dynamique, la copie des objets et la surcharge des opérateurs. Nous disposons maintenant d’un nouvel outil : les templates. Nous allons reprendre notre classe Matrice afin de la rendre indépendante du type particulier utilisé pour représenter ses coefficients. Ce chapitre constituera ainsi une synthèse de nombreuses notions étudiées dans cette partie. Pourquoi rendre Matrice générique ? Dans notre première version de la classe Matrice, les coefficients étaient représentés par des valeurs Nous avions donc notamment un attribut de la forme : double* coefficients; Ce choix signifie que toutes les matrices construites à partir de cette classe possèdent nécessairement des coefficients de type double. Nous pouvons cependant souhaiter manipuler différentes catégories de matrices. • une matrice d’entiers ; • une matrice de nombres réels représentés par des float ; • une matrice de nombres réels représentés par des double. Une première solution consisterait à écrire plusieurs classes presque identiques, chacune adaptée à un type particulier. Cette solution entraînerait une duplication importante du code. Le type des coefficients devient un paramètre Plutôt que de fixer le type des coefficients lors de l’écriture de la classe, nous pouvons en faire un paramètre de type : template <typename T> Le paramètre T représentera désormais le type des coefficients de la matrice. 2026 – Partie II C++ Faire évoluer progressivement la classe Transformer Matrice en classe template ne consiste cependant pas simplement à remplacer une occurrence de double par T. Le type des coefficients intervient dans plusieurs éléments de la classe : attributs, paramètres, valeurs retournées, allocation dynamique et opérateurs. Nous allons donc reprendre progressivement notre classe existante et examiner chaque élément afin de déterminer s’il dépend ou non du paramètre de type T. exercice : Classe Matrice pour des nb complexes Transformer la classe Matrice Nous allons maintenant transformer progressivement notre classe Matrice en une classe template. La première modification consiste à faire précéder sa déclaration par : template <typename T> La classe devient donc : template <typename T> À ce stade, T représente le type des coefficients qui sera choisi lors de l’instanciation de la classe. Quels éléments doivent changer ? Tous les éléments de la classe ne dépendent pas du type des coefficients. Par exemple, le nombre de lignes et le nombre de colonnes restent des entiers : Il n’y aurait évidemment aucun sens à les remplacer par des objets de type T. En revanche, notre ancien attribut : double* coefficients; dépend directement du type choisi pour représenter les coefficients. Le pointeur coefficients ne pointe donc plus nécessairement vers un tableau de double. Son type dépend maintenant de l’instanciation de la classe : Matrice<int> A(3, 3); Matrice<double> B(3, 3); Dans le premier cas, coefficients est un pointeur vers int. Dans le second, il s’agit d’un pointeur
Accueil
2026 – Partie II C++ Les fonctions qui manipulent un coefficient Les fonctions qui manipulent directement les coefficients doivent également être rendues indépendantes du type utilisé. Notre fonction permettant de lire un coefficient retournait auparavant un Elle retourne maintenant un objet de type T : De même, la fonction permettant de modifier un coefficient reçoit maintenant une valeur de type T : void setCoefficient( En revanche, les paramètres ligne et colonne restent des entiers : ils représentent des indices et non des coefficients. Le passage à une classe template ne consiste donc pas à remplacer systématiquement tous les types par T. Il faut déterminer quels éléments dépendent réellement du paramètre de type. L’allocation dynamique Le constructeur doit lui aussi tenir compte du type des coefficients. Dans notre ancienne classe, le tableau dynamique était créé par une instruction de la forme : new double[lignes * colonnes]{}; Le type double étant maintenant remplacé par le paramètre de type T, cette allocation devient : new T[lignes * colonnes]{}; Ainsi, une instanciation : Matrice<int> A(5, 3); entraînera une allocation d’un tableau de int, tandis que : Matrice<double> B(5, 3); entraînera une allocation d’un tableau de double. Le mécanisme d’allocation dynamique n’a pas changé. Seul le type des objets alloués est devenu Les définitions dans le fichier Matrice.tpp Dans une classe ordinaire, nous avons pris l’habitude de séparer la déclaration de la classe, placée dans un fichier d’en-tête .h, de la définition de ses méthodes, placée dans un fichier .cpp. Pour une classe template, la situation est différente. Lorsqu’il instancie une classe telle que :
Accueil
2026 – Partie II C++ Matrice<double> A(3, 3); le compilateur doit disposer non seulement de la déclaration du template, mais également de la définition des méthodes nécessaires à cette instanciation. Nous allons donc conserver la séparation entre les déclarations et les définitions, mais placer ces dernières dans un fichier : À la fin du fichier Matrice.h, nous écrivons alors : #include « Matrice.tpp » Le contenu du fichier Matrice.tpp devient ainsi visible chaque fois que le fichier Matrice.h est L’extension .tpp n’a aucune signification particulière pour le langage C++. Il s’agit d’une convention permettant de distinguer les définitions des méthodes d’une classe template des fichiers source .cpp ordinaires. Il serait également possible de placer directement toutes les définitions dans le fichier Dans notre ancienne classe, la définition du constructeur commençait par : Matrice::Matrice(int lignes, int colonnes) La classe étant maintenant un template, la définition doit préciser le paramètre de type utilisé : template <typename T> Matrice<T>::Matrice(int lignes, int colonnes) La première ligne rappelle que la définition qui suit appartient à un template utilisant le paramètre indique ensuite que la méthode appartient à l’instanciation Matrice<T>. Il faut donc bien distinguer : template <typename T> qui déclare le template de classe, de : Matrice<T>::Matrice(…) qui définit une méthode appartenant à une instanciation de ce template. Le constructeur peut alors être écrit :
Accueil
2026 – Partie II C++ template <typename T> Matrice<T>::Matrice(int lignes, int colonnes) : nombreLignes(lignes), nombreColonnes(colonnes), new T[lignes * colonnes]{} Lors de l’instanciation : Matrice<int> A(5, 3); le compilateur peut donc générer une version du constructeur dans laquelle T correspond à int. Matrice<double> B(5, 3); T correspond cette fois à double. Le constructeur de copie Le passage à une classe template ne modifie pas le principe de la copie profonde étudié précédemment. Chaque matrice doit toujours posséder sa propre zone de mémoire contenant ses coefficients. Le constructeur de copie devient : template <typename T> Matrice<T>::Matrice( const Matrice<T>& autre autre.nombreColonnes autre.getCoefficient(i, j) const Matrice<T>& autre indique que la matrice copiée possède le même type de coefficients que la matrice construite. Ainsi, le constructeur de copie d’une :
Accueil
2026 – Partie II C++ reçoit ici une autre Matrice<double>. • Rendre la classe générique ne change pas le principe de la copie profonde. • Une nouvelle zone mémoire est toujours allouée pour le nouvel objet, puis les coefficients sont copiés dans cette zone. L’opérateur d’affectation L’opérateur d’affectation doit subir la même adaptation. Son type de retour et son paramètre font maintenant référence à Matrice<T> : template <typename T> Matrice<T>& Matrice<T>::operator=( const Matrice<T>& autre delete[] coefficients; autre.nombreColonnes; autre.getCoefficient(i, j)
Accueil
2026 – Partie II C++ On retrouve exactement les étapes étudiées lors de la copie des objets : 1. vérifier qu’il ne s’agit pas d’une auto-affectation ; 2. libérer l’ancien tableau dynamique ; 3. recopier les dimensions ; 4. allouer un nouveau tableau ; 5. recopier les coefficients ; 6. retourner l’objet courant par référence. La seule modification véritablement liée au template concerne l’allocation : new T[nombreLignes * nombreColonnes] new double[nombreLignes * nombreColonnes] Le destructeur ne dépend pratiquement pas du type des coefficients. Sa définition doit cependant respecter la syntaxe d’une classe template : template <typename T> Matrice<T>::~Matrice() delete[] coefficients; L’instruction delete[] détruit les éléments du tableau puis libère la mémoire qui lui avait été attribuée. Le type réel des éléments dépend de T, mais le principe de gestion de la mémoire reste La gestion de la mémoire ne change pas. C’est le type des objets stockés qui devient générique. Les opérations sur les coefficients Une fois la gestion de la mémoire adaptée au paramètre de type T, nous pouvons examiner les méthodes qui manipulent directement les coefficients de la matrice. Pour certaines d’entre elles, la transformation est presque immédiate : il suffit de remplacer le type particulier utilisé précédemment par le paramètre de type T. Dans notre première version de la classe, la méthode getCoefficient() retournait une valeur de Le type des coefficients étant maintenant représenté par T, son type de retour doit lui aussi devenir template <typename T> T Matrice<T>::getCoefficient(
Accueil
2026 – Partie II C++ return coefficients[ ligne * nombreColonnes + colonne Ainsi, pour une matrice : Matrice<int> A(3, 3); la méthode retourne un int, tandis que pour : Matrice<double> B(3, 3); elle retourne un double. Le type retourné dépend donc directement de l’instanciation du template. Modifier un coefficient La même transformation s’applique à la méthode setCoefficient(). La valeur à enregistrer dans la matrice doit être du type correspondant aux coefficients : template <typename T> void Matrice<T>::setCoefficient( ligne * nombreColonnes + colonne Les paramètres ligne et colonne restent naturellement de type int, puisqu’ils représentent des En revanche, le paramètre valeur dépend du type des coefficients et doit donc être de type T. Les indices décrivent la structure de la matrice : ils restent des entiers. La valeur décrit son contenu : son type devient T. Afficher une matrice La méthode permettant d’afficher une matrice nécessite très peu de modifications : template <typename T> void Matrice<T>::afficherMatrice() const for (int i = 0; i < nombreLignes; ++i) for (int j = 0; j < nombreColonnes; ++j) << getCoefficient(i, j)
Accueil
2026 – Partie II C++ std::cout << std::endl; Le code de la méthode ne dépend explicitement d’aucun type particulier. La méthode getCoefficient() retourne automatiquement une valeur du type correspondant à l’instanciation de la matrice. Il existe cependant une condition implicite : l’expression std::cout << getCoefficient(i, j); doit être valide pour le type T utilisé. Cette condition est naturellement satisfaite pour les types usuels tels que int, float ou double. Transposer une matrice La transposition constitue un exemple particulièrement simple de généralisation. Cette opération ne modifie pas les coefficients et n’effectue aucune opération arithmétique sur eux. Elle échange simplement leurs positions. La méthode devient : template <typename T> Matrice<T> Matrice<T>::transposee() const Matrice<T> resultat( for (int i = 0; i < nombreLignes; ++i) for (int j = 0; j < nombreColonnes; ++j) resultat.setCoefficient( getCoefficient(i, j) indique que la matrice transposée possède le même type de coefficients que la matrice d’origine. Matrice<int> A(5, 3); auto B = A.transposee();
Accueil
2026 – Partie II C++ produit une matrice transposée de type Matrice<int> et de dimensions 3 × 5. De même, la variable locale : Matrice<T> resultat( est construite avec le même paramètre de type T. Une généralisation presque mécanique Les méthodes étudiées dans cette section ont pu être adaptées sans modifier leur algorithme. Le type double a été remplacé par T lorsque cela était nécessaire, mais les opérations réalisées sur la matrice sont restées identiques. Cette situation n’est cependant pas générale. Certaines méthodes effectuent elles-mêmes des calculs sur les coefficients. Elles peuvent alors contenir des hypothèses implicites sur leur type. La multiplication de deux matrices va nous fournir un premier exemple particulièrement intéressant La multiplication et le type T Les méthodes étudiées jusqu’à présent ont pu être rendues génériques sans modifier fondamentalement leur fonctionnement. La multiplication de deux matrices fait apparaître une difficulté supplémentaire. Dans notre ancienne classe, dont les coefficients étaient de type double, nous avions utilisé une variable permettant d’accumuler les différents produits : Cette écriture était parfaitement adaptée à une matrice dont les coefficients étaient nécessairement Ce n’est plus le cas avec Matrice<T>. Une hypothèse cachée dans l’algorithme Considérons deux matrices : Matrice<int> A(5, 3); Matrice<int> B(3, 5); Les coefficients des deux matrices sont des int. Le produit de ces deux matrices doit donc, dans notre implémentation, produire une nouvelle matrice dont les coefficients sont également des int. introduirait alors artificiellement un double dans un calcul effectué sur des coefficients de type int. Plus généralement, nous ne devons plus supposer que le type utilisé pour effectuer cette somme est double. Il doit dépendre du paramètre de type du template.
Accueil
2026 – Partie II C++ Une première transformation consiste donc à remplacer le type double par T : La variable somme possède ainsi le même type que les coefficients de la matrice. Il reste cependant à lui attribuer une valeur initiale. Pour calculer un coefficient du produit de deux matrices, cette variable doit initialement représenter L’initialisation par défaut Nous pourrions être tentés d’écrire : Cette écriture suppose cependant que le type T peut être initialisé à partir de la valeur entière 0. Une écriture plus générique consiste à utiliser l’initialisation par accolades : Pour les types numériques usuels, cette initialisation produit la valeur nulle correspondante. nous obtenons conceptuellement : Dans les deux cas, somme est initialisée à zéro. initialisation par défaut adaptée au type T La multiplication générique Nous pouvons maintenant adapter la méthode complète : template <typename T> Matrice<T> Matrice<T>::multiplier( const Matrice<T>& autre Matrice<T> resultat( autre.nombreColonnes for (int i = 0; i < nombreLignes; ++i)
Accueil
2026 – Partie II C++ j < autre.nombreColonnes; getCoefficient(i, k) autre.getCoefficient(k, j); resultat.setCoefficient( La structure de l’algorithme n’a pas changé. En revanche, les opérations effectuées sur les coefficients dépendent maintenant du type T. getCoefficient(i, k) * autre.getCoefficient(k, j) suppose que deux objets de type T peuvent être multipliés. suppose que le résultat obtenu peut être ajouté à un objet de type T. Un template impose des exigences au type utilisé template <typename T> ne signifie pas que n’importe quel type pourra nécessairement être utilisé avec toutes les méthodes Le code de chaque méthode effectue certaines opérations sur les objets de type T. Ces opérations doivent avoir un sens pour le type choisi lors de l’instanciation. Dans le cas de la multiplication de matrices, le type T doit notamment permettre les opérations nécessaires à l’initialisation, à la multiplication et à l’addition des coefficients.
Accueil
2026 – Partie II C++ Un template rend un type paramétrable, mais le code utilisé impose des opérations que ce type doit supporter. Par exemple, nous pouvons maintenant écrire : Matrice<int> A(5, 3); Matrice<int> B(3, 5); C.afficherMatrice(); auto C = A.multiplier(B); Le compilateur instancie alors la méthode multiplier() pour le type int. devient dans cette instanciation une variable de type int. Chaque instanciation constitue un type différent Lorsque nous écrivons : template <typename T> nous ne définissons pas une classe unique capable de changer de type pendant l’exécution du programme. Nous définissons un modèle à partir duquel le compilateur peut produire différentes classes Considérons par exemple : Matrice<int> A(3, 3); Matrice<double> B(3, 3); Le compilateur instancie le template avec deux paramètres de type différents. Nous obtenons donc deux types distincts : Dans le premier cas, le paramètre T correspond à int. Dans le second, il correspond à double. Matrice<int> ̸= Matrice<double> Deux classes concrètes différentes Pour une instanciation de type : les éléments dépendant de T sont adaptés au type int. Nous obtenons notamment conceptuellement :
Accueil
2026 – Partie II C++ et une méthode telle que : correspond alors à une méthode retournant un int. le même template conduit cette fois à un stockage de coefficients de type double et à des méthodes adaptées à ce type. Une conséquence importante Le fait que le langage C++ sache convertir une valeur de type int en une valeur de type double ne signifie pas que les classes obtenues à partir de ces deux types soient elles-mêmes interchangeables. utilise une conversion parfaitement définie entre deux types numériques. En revanche, les types : sont deux types de classes distincts. La possibilité de convertir les coefficients ne crée pas automatiquement une conversion entre les matrices qui les contiennent. Matrice<int> −→ Matrice<double> Il serait possible de concevoir notre classe de manière à autoriser certaines conversions entre différentes instanciations de Matrice. Cela nécessiterait cependant de définir explicitement le mécanisme permettant de réaliser cette conversion. Nous reviendrons sur ce type de situation dans les exercices. Hériter d’une classe template Notre classe MatriceCarree avait été construite comme une spécialisation de la classe Matrice. Une matrice carrée reste en effet une matrice, mais son nombre de lignes est nécessairement égal à son nombre de colonnes. Dans notre première implémentation, nous pouvions donc écrire : class MatriceCarree : public Matrice La classe de base Matrice étant maintenant devenue un template, cette déclaration doit être adaptée.
Accueil
2026 – Partie II C++ Une classe template dérivée d’une classe template Une matrice carrée doit elle aussi pouvoir contenir différents types de coefficients. Nous transformons donc MatriceCarree en classe template : template <typename T> class MatriceCarree : public Matrice<T> Le même paramètre de type T est transmis à la classe de base. Ainsi, une instanciation : MatriceCarree<int> A(3); MatriceCarree<double> B(3); MatriceCarree<double> −→ Matrice<double> Le paramètre T ne représente donc pas un nouveau type propre à la classe dérivée. Il est transmis à la classe de base afin que les deux classes manipulent le même type de coefficients. Appeler le constructeur de la classe de base Le constructeur d’une matrice carrée ne reçoit qu’une dimension, puisque son nombre de lignes et son nombre de colonnes sont identiques. Sa définition devient : template <typename T> MatriceCarree<T>::MatriceCarree(int ordre) : Matrice<T>(ordre, ordre) Comme précédemment, la ligne : template <typename T> indique que la définition appartient à un template. MatriceCarree<T>::MatriceCarree désigne le constructeur de la classe dérivée.
Accueil
2026 – Partie II C++ Matrice<T>(ordre, ordre) appelle le constructeur de la classe de base en lui transmettant deux dimensions identiques. Accéder aux membres hérités L’association entre templates et héritage fait apparaître une particularité du langage C++. Considérons la méthode permettant d’obtenir l’ordre d’une matrice carrée. Nous pourrions être tentés d’écrire : template <typename T> int MatriceCarree<T>::getOrdre() const return getNombreLignes(); La méthode getNombreLignes() est bien héritée de Matrice<T>. Cependant, Matrice<T> est une classe de base dont le type dépend du paramètre template T. Dans cette situation, lors de la première analyse du template, le compilateur ne recherche pas automatiquement certains noms dans une classe de base dépendante du paramètre de type. Nous pouvons lui indiquer explicitement que la fonction recherchée est un membre de l’objet courant La méthode devient alors : template <typename T> int MatriceCarree<T>::getOrdre() const return this->getNombreLignes(); this->getNombreLignes() L’écriture this-> indique explicitement au compilateur que getNombreLignes() doit être recherchée parmi les membres de l’objet courant, ce qui inclut les membres hérités de Matrice<T>. Cette syntaxe apparaît fréquemment lorsqu’une classe template dérive d’une classe dont le type dépend lui-même d’un paramètre template. Cette particularité n’est pas liée au fonctionnement mathématique de notre matrice. Elle provient des règles utilisées par le compilateur pour analyser les templates. Il n’est pas nécessaire d’en mémoriser immédiatement tous les détails. Il faut surtout retenir que l’héritage d’une classe template peut rendre nécessaire l’écriture this-> pour accéder à certains membres hérités.
Accueil
2026 – Partie II C++ Construire un mineur La méthode mineur() construit elle-même une nouvelle matrice carrée. Son type de retour doit donc tenir compte du paramètre de type : template <typename T> MatriceCarree<T> MatriceCarree<T>::mineur( int colonneSupprimee int ordre = this->getNombreLignes(); MatriceCarree<T> resultat(ordre – 1); int ligneMineur = 0; for (int i = 0; i < ordre; ++i) if (i == ligneSupprimee) int colonneMineur = 0; for (int j = 0; j < ordre; ++j) if (j == colonneSupprimee) resultat.setCoefficient( this->getCoefficient(i, j) La matrice obtenue doit conserver le type des coefficients de la matrice d’origine. Une MatriceCarree<int> produit donc ici un mineur de type MatriceCarree<int>, tandis qu’une MatriceCarree<double> produit un MatriceCarree<double>. À l’intérieur de ce contexte, le langage permet également dans certaines situations d’utiliser simplement le nom : pour désigner l’instanciation courante MatriceCarree<T>. L’écriture explicite :
Accueil
2026 – Partie II C++ sera néanmoins privilégiée ici afin de rendre le rôle du paramètre de type immédiatement visible. Notre méthode de calcul du déterminant peut continuer à utiliser les méthodes héritées de Matrice<T>. Sa définition commence par : template <typename T> double MatriceCarree<T>::determinant() const int ordre = getOrdre(); Nous avons choisi de conserver double comme type de retour du déterminant. Ce choix convient aux types numériques utilisés dans nos exemples et permet de conserver une Il ne s’agit cependant pas d’une obligation imposée par les templates. Une classe encore plus générique pourrait chercher à retourner une valeur dépendant elle aussi du type T. Une telle généralisation imposerait alors d’examiner plus précisément les opérations disponibles pour ce type. Exercice : adapter la classe afin d’étudier le cas d’une matrice dont les coefficients sont des nombres complexes. Examiner en particulier le type retourné par la méthode determinant(). Templates et héritage La transformation de MatriceCarree montre que les templates s’intègrent naturellement aux mécanismes de la programmation orientée objet. Une classe template peut dériver d’une autre classe template et lui transmettre son paramètre de template <typename T> class MatriceCarree : public Matrice<T> Les principes de l’héritage restent inchangés. Certaines particularités syntaxiques apparaissent cependant lorsque la classe de base dépend du paramètre template, notamment lors de l’accès aux Les templates ne remplacent pas l’héritage : ils permettent de le rendre lui aussi générique. La transformation de notre classe Matrice en Matrice<T> nous a permis d’appliquer les templates à une classe déjà relativement complète.
Accueil
2026 – Partie II C++ Contrairement aux premiers exemples de templates étudiés précédemment, nous ne sommes plus partis d’une fonction ou d’une petite classe créée spécialement pour découvrir le mécanisme. Nous avons repris une classe existante comportant déjà de nombreux éléments de la programmation orientée • des constructeurs et un destructeur ; • une allocation dynamique ; • un constructeur de copie ; • un opérateur d’affectation ; • des méthodes effectuant des opérations sur les données ; • une classe dérivée. Le passage aux templates n’a pas remis en cause ces mécanismes. Il a permis de remplacer un type fixé lors de l’écriture de la classe par un paramètre de type choisi lors de son utilisation. D’une classe à une famille de classes Notre ancienne classe imposait le type double à ses coefficients. template <typename T> nous définissons désormais un modèle permettant au compilateur de produire différentes classes Ces différentes instanciations utilisent le même algorithme général, mais manipulent des coefficients de types différents. Nous évitons ainsi d’écrire plusieurs classes dont le fonctionnement serait pratiquement identique. Le code générique n’est pas nécessairement universel Nous avons également constaté qu’un template ne rend pas automatiquement un programme compatible avec n’importe quel type. Une méthode telle que la multiplication utilise notamment des opérations de la forme : Le type utilisé pour instancier le template doit donc permettre ces opérations.
Accueil
2026 – Partie II C++ nous a également montré qu’une généralisation peut nécessiter de réexaminer certaines hypothèses qui étaient presque invisibles lorsque nous travaillions exclusivement avec des double. Écrire du code générique ne consiste pas seulement à remplacer un type par T. Il faut également examiner les opérations que le programme exige de ce type. Templates et programmation orientée objet La classe MatriceCarree<T> nous a finalement permis de vérifier que les templates s’intègrent aux mécanismes de l’héritage. Nous avons ainsi pu écrire : template <typename T> class MatriceCarree : public Matrice<T> Une classe template peut donc dériver d’une autre classe template tout en lui transmettant son Cette association entre templates et héritage introduit quelques particularités syntaxiques, comme l’utilisation possible de this-> pour accéder à certains membres d’une classe de base dépendant du paramètre template. Les principes fondamentaux de la programmation orientée objet restent cependant inchangés. Une synthèse de la programmation orientée objet Notre classe Matrice<T> constitue finalement une synthèse d’une grande partie des notions rencontrées dans cette partie. Elle associe notamment : • encapsulation des données ; • construction et destruction des objets ; • allocation dynamique ; • copie profonde ; • surcharge des opérateurs ; • et maintenant programmation générique à l’aide des templates. Nous sommes partis d’une classe capable de manipuler uniquement des matrices de double. Nous disposons maintenant d’un modèle de classe pouvant être adapté par le compilateur au type de coefficients demandé par le programme. Les templates constituent ainsi un outil puissant pour écrire du code réutilisable sans renoncer au typage strict du C++.
Accueil
2026 – Partie II C++ Jusqu’à présent, notre classe Matrice<T> gère elle-même la mémoire dynamique qu’elle utilise. Elle doit explicitement allouer cette mémoire : delete[] coefficients; Cette manière de procéder nous a permis de comprendre précisément les mécanismes mis en jeu : durée de vie des objets, allocation dynamique, destruction et copie profonde. Le C++ moderne propose cependant des outils permettant de conserver ce contrôle tout en confiant davantage de responsabilités aux objets eux-mêmes. Nous allons maintenant pouvoir étudier ces mécanismes et découvrir notamment le principe fondamental du RAII, ainsi que les outils modernes de gestion automatique des ressources. Nous avons appris à gérer explicitement les ressources. Nous pouvons maintenant apprendre à laisser les objets C++ les gérer pour nous.
Accueil
Accueil
Termes à ajouter au glossaire

Codes et programmes contenus dans le chapitre

Matrice Template
Classe Matrice sous forme de template
README.pdf
Explications sur l’utilisation des codes
Contenu

Inscription

×
Cancel