[31] Classes conteneurs et templates

(Une partie de C++ FAQ Lite fr, Copyright © 1991-2002, Marshall Cline, cline@parashift.com)

Traduit de l'anglais par Jérôme Lecomte

Les FAQs de la section [31]


[31.1] Comment puis-je faire un tableau associatif à la perl en C++?
Utilisez la classe template standard map<Key,Val>:

#include <string>
#include <map>
#include <iostream>

using namespace std;

int main()
{
map<string, int, less<string > > age; / / age est une map de string à int

age["Fred"] = 42; / / Fred a 42 ans de
age["Barney"] = 37; / / Barney a 37 ans

if(todayIsFredsBirthday()) / / à l'anniversaire de Fred.
++age["Fred"]; // incrémente l'âge de Fred.

cout << "Fred a " << age["Fred"] << " an(s)\n";
}

[ Haut | Bas | Rechercher ]


[31.2] Comment puis-je créer un <conteneur préféré> d'objets de types différents?
Vous ne pouvez pas, mais vous pouvez le simuler assez bien. En C/C++ tous les tableaux sont homogènes (c.-à-d., les éléments sont tout de même type). Cependant, avec un niveau supplémentaire d'indirection, vous pouvez donner l'aspect d'un conteneur hétérogène (un conteneur hétérogène est un conteneur où les objets contenus sont de types différents).

Il y a deux technique pour créer des conteneurs hétérogènes.

La première technique s'utilise lorsque les objets que vous voulez entreposer dans le conteneur sont publiquement dérivés d'une classe de base commune. Vous pouvez dans ce cas déclarer/définir votre conteneur pour qu'il stoque des pointeurs sur la classe de base. Vous placez alors indirectement les objets dérivés de classe dans le conteneur en enregistrant l'adresse de l'objet comme élément dans le conteneur. Vous pouvez alors accéder aux objets dans le conteneur indirectement via pointeurs (en appréciant le polymorphisme). Si vous avez besoin de connaître le type exact de l'objet dans le conteneur vous pouvez utiliser dynamic_cast< > ou typeid(). Vous aurez besoin probablement de l'idiome du constructeur virtuel pour copier un conteneur de types disparates d'objet. L'inconvénient de cette approche est la gestion de la mémoire qui devient un peu plus problématique (qui "possède" les objets pointés? si vous deletez ces objets pointés quand vous détruisez le conteneur, comment pouvez-vous garantir que personne d'autre a une copie d'un de ces pointeurs? si vous ne deletez pas les objets pointés quand vous détruisez le conteneur, comment pouvez vous assurez vous que quelqu'un d'autre le fera par la suite?). La copie de conteneur est également rendu plus complexe (elle peut réellement casser les fonctions de copie de conteneur puisque vous ne voulez pas copier les pointeurs, au moins quand le conteneur "possède" les objets pointés).

La deuxième technique s'utilise quand les types d'objet sont disjoints -- ils ne partagent pas une classe de base commune. L'approche ici est d'utiliser une classe de handles (bâtons en français). Le conteneur est un conteneur d'objets handle (par valeur ou par pointeur, selon votre goût; par valeur est plus facile). Chaque objet handle sait comment "maintenir" (c.-à-d., manipuler le pointeur) la variable sous-jacente que vous voulez mettre dans le conteneur. Vous pouvez utiliser une classe handle simple avec plusieurs types de pointeurs différents comme données, ou une hiérarchie de classes handle qui masquent les divers types que vous souhaitez contenir (requiert que le conteneur soit un conteneur de pointeurs sur la classe de base de la classe handle). L'inconvénient de cette approche est que la (les) classe(s) handle devront être modifiées chaque fois que vous changerez l'ensemble des types qui peuvent être réferencés. L'avantage est que vous pouvez utiliser la (les) classe(s) handle pour encapsuler la partie laide de la gestion de mémoire et du temps de vie de l'objet. De ce fait l'utilisation des objets handle peut être salutaire même dans le premier cas.

[ Haut | Bas | Rechercher ]


[31.3] Comment puis-je inserer/acceder/modifier les éléments d'une liste/table de hachage/etc ?
J'utiliserai une "insertion dans une liste" comme exemple. Il est facile de permettre l'insertion en tête ou en fin de liste, mais la limitation à ces derniers produirait une bibliothèque trop faible (une bibliothèque faible est presque plus mauvaise qu'aucune bibliothèque).

Cette réponse sera difficile à avaler pour le débutant en C++, ainsi je donnerai plusieurs possibilités. La première option est la plus facile; le deuxième et la troisièmement sont meilleures.

  1. Munissez votre liste d'un "emplacement courant" et de fonctions membre comme avance(), revient(), fin(),debut(), getElemCourant(), setElemCourant(Element), insereElem(Element), et retireElem(). Bien que ceci fonctionne dans de petits exemples, la notion d'une position actuelle rend difficile l'accès à des éléments stockés en deux positions ou plus dans la liste (par exemple, "pour toutes les paires x,y faire ..."
  2. Retirez les fonctions ci-dessus des membres de Liste, et déplacez les dans une classe séparée : ListPosition. ListPosition agit comme une "position courante" dans la liste. Ceci permet des positions multiples dans une même liste. En faisant de ListPosition un friend (ami) de la classe Liste, vous cachez l'implémentation de Liste du monde extérieur (autrement l'interface de Liste utiliée par ListePosition devrait être déclarée dans la section public de Liste). Note: ListePosition peut utiliser la surcharge d'opérateur pour des fonctionalités comme avance() et revient(), puisque la surcharge d'opérateur est sucre syntaxique pour des fonctions membres normales.
  3. Considerez l'itération dans son ensemble comme un évenement atomique, et créez une classe template pour encapsuler cette évenement. Ceci améliore la performance en rendant possible l'élimination d'accès à des fonctions membres publique (éventuellement déclarées virtual ) qui surviennent de surcroît souvent à l'intérieur de boucle. Malheureusement la classe template augmentera la taille de votre code, pusique les templates gagne du temps en dupliquant le code. Pour plus d'information, voir [Koenig, "Templates as interfaces," JOOP, 4, 5 (Sept 91)], et [Stroustrup, "The C++ Programming Language Third Edition," under "Comparator"].

[ Haut | Bas | Rechercher ]


[31.4] Quelle est l'idée derrière les templates (patrons comme en couture)?
Un template est un découpeur de biscuit qui spécifie comment couper des biscuits qui se ressemblent à peu près tous (bien que les biscuits puissent être faits de différentes sortes de pâte, ils auront tous la même forme de base). De la même façon, une classe template est un découpeur de gateau pour une manière de construire une famille de classes qui se ressemblent toutes, et une fonction template décrit comment construire une famille de fonctions similaires les unes aux autres.

Les classes templates sont souvent utilisées pour construire des conteneurs de type sans danger (bien que ceci ne fait qu'égratigner la surface de ce pouquoi elles peuvent être utilisées).

[ Haut | Bas | Rechercher ]


[31.5] Quelle est la syntaxe/sémantique d'une "fonction template" (fonction de patron)?
Considérez cette fonction qui swap deux arguments de type entier :

void swap(int& x, int& y) // (swap == échange)
{
int tmp = x;
x = y;
y = tmp;
}

Si nous avions aussi à échanger des floats (flottants), des longs, des string (chaînes de caractères), des sets (ensembles), et des systèmes de fichiers, nous serions vite las de coder ces lignes qui n'ont de différent que le type. La répétition sans réflexion est un travail idéal pour un ordinateur, et donc pour une fonction template:

template<class T>
void swap(T& x, T& y)
{
T tmp = x;
x = y;
y = tmp;
}

Chaque fois que nous utilisons swap() avec une différente paire de types, le compilateur ira chercher la définition ci-dessus et crééra une nouvelle fonction de patron comme instance de celle-ci. E.g.,

int main()
{
int i,j; /*...*/ swap(i,j); // Instancie swap pour int
float a,b; /*...*/ swap(a,b); // Instancie swap pour float
char c,d; /*...*/ swap(c,d); // Instancie swap pour char
String s,t; /*...*/ swap(s,t); // Instancie swap pour String
}

Note: Une "template fonction" (fonction de patron) est l'instanciation d'une "fonction template (patron de fonction)".

[ Haut | Bas | Rechercher ]


[31.6] Quelle est la syntaxe/sémantique d'une "classe template" (patron de classe)?
Considérez une classe conteneur Array(tableau) qui se comporte comme un tableau d'entier:

// Ceci irait dans un fichier d'en-tête tel que "Array.h"
class Array {
public:
Array(int len=10) : len_(len), data_(new int[len]) { }
~Array() { delete [] data_; }
int len() const { return len_; }
const int& operator[](int i) const { return data_[check(i)]; }
int& operator[](int i) { return data_[check(i)]; }
Array(const Array&);
Array& operator= (const Array&);
private:
int len_;
int* data_;
int check(int i) const
{ if (i < 0 || i >= len_) throw BoundsViol("Array", i, len_);
return i; }
};

Tout comme swap() ci-dessus, répéter le même code encore et encore pour des tableau de float, de char, de string, de tableau-de-string, etc, deviendra rapidement fastidieux.

// Ceci irait dans un fichier d'en-tête tel que "Array.h"
template<class T>
class Array {
public:
Array(int len=10) : len_(len), data_(new T[len]) { }
~Array() { delete [] data_; }
int len() const { return len_; }
const T& operator[](int i) const { return data_[check(i)]; }
T& operator[](int i) { return data_[check(i)]; }
Array(const Array<T>&);
Array<T>& operator= (const Array<T>&);
private:
int len_;
T* data_;
int check(int i) const
{ if (i < 0 || i >= len_) throw BoundsViol("Array", i, len_);
return i; }
};

Contrairement aux fonctions de patron, les classes de patron (instantiations de classes template) ont besoin d'être explicitement paramêtrées:

int main()
{
Array<int> ai;
Array<float> af;
Array<char*> ac;
Array<String> as;
Array< Array<int> > aai;
}

Notez l'espace entre les deux >'s dans le dernier exemple. Sans cet espace, le compilateur verrait un >> (symbol de décalage binaire) au lieu de deux >'s.

[ Haut | Bas | Rechercher ]


[31.7] Qu'est ce qu'un "type paramêtré"?
Une autre façon de dire, "classe template."

Un type paramêtré est un type qui est paramêtré par un aute type ou une valeur. List<int> est un type (List) paramêtré par un autre type (int).

[ Haut | Bas | Rechercher ]


[31.8] Qu'est-ce que la "généricité"?
Encore un autre moyen de dire, "classes template."

A ne pas confonfre avec "généralité" (qui signifie seulement l'évitement de solutions trop spécifiques), "généricité" signifie classes template.

[ Haut | Bas | Rechercher ]


E-mail Marshall Cline Ecrire à l'auteur, au traducteur, ou en savoir plus sur la traduction.
C++ FAQ Lite fr | Table des matières | Index | A propos de l'auteur | © | Téléchargez votre propre copie ]
Dernière révision Sun Apr 13 23:54:08 PDT 2003