#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 ]
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 ]
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.
[ Haut | Bas | Rechercher ]
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 ]
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 ]
//
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 ]
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 ]
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 ]
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