Utilisation de la technique des "traits"

Description

Remarque préliminaire: les classes utilisées dans cet exemple n'effectuent aucun contrôle et ne gèrent aucune exception. Elles ne sont données qu'en exemple.
Les "traits" permettent de définir les caractéristiques de types afin de pouvoir généraliser leur utilisation.
Imaginons que nous voulions définir une classe template contenant des données que nous appellerons "Variable".
Nous pouvons la définir comme suit:

template<class T>
class Variable{
friend operator==(const Variable<T>& v1, const Variable<T>& v2){
return (v1._donnee==v2.donnee);
}
private:
T _donnee;
public:
Variable(T);
Variable(const Variable<T>&);
~Variable();
Variable<T>& operator=(const Variable<T>&);
Variable<T>& operator=(const T&);
T valeur()const;
};

voici son implémentation:

template<class T>
Variable<T>::Variable(T donnee)
:_donnee(donnee){

}

template<class T>
Variable::Variable(const Variable<T>& v)
:_donnee(v._donnee){

}

template<class T>
Variable<T>::~Variable(){

}

template<class T>
Variable<T>& Variable<T>::operator=(const Variable<T>& v){
_donnee=v._donnee;
}

template<class T>
T valeur()const{
return _donnee;
}

Si nous utilisons un type int ou float, par exemple, pas de problème. Mais qu'advient-il si on tente d'utiliser un type char*, par exemple ?
Là intervient la technique des traits.

Déclarons la structure suivante:

template<class T>
struct Trait{
typedef T trait_type;
void assign(T&, T);
bool eq(T, T);
bool ls(T, T);
bool gt(T, T);
void free(T&);
};

Voici son implémentation:

template<class T>
void Trait<T>::assign(T& t1, T t2){
t1=t2;
}

template<class T>
bool Trait<T>::eq(T, T){
return (t1._donnee==t2._donnee);
}

template<class T>
bool Trait<T>::ls(T, T){
return (t1._donnee<t2._donnee);
}

template<class T>
bool Trait<T>::gt(T, T){
return(t1._donnee>t2._donnee);
}

template<class T>
void Trait<T>::free(T&){
//rien à faire
}

Nous avons vu que le type char*, par exemple, pouvait poser problème. Spécialisons donc la struct pour le type char*:

template<>
void Trait<char*>::assign(char*& s1, char* s2){
if(s1)
delete [] s1;
s1=new char[strlen(s2)+1];
strcpy(s1, s2);
}

template<>
bool Trait<char*>::eq(char* s1, char* s2){
return (strcmp(s1, s2)==0);
}

template<>
bool Trait<char* s1, char* s2){
return (strcmp(s1, s2)<0);
}

template<>
bool Trait<char*>::gt(char* s1, char* s2){
return (strcmp(s1, s2)>0);
}

template<>
void Trait<char*>::free(char* s){
// Ici, quelque chose à faire !
delete [] s;
s=NULL;
}

Nous pouvons donc redéfinir notre classe variable comme suit:

template<class T, class traits=Trait<T> >
class Variable{
friend bool operator==(const Variable<T, traits>& v1, const Variable<T, traits>& v2){
traits t;
return (t.eq(v1._donnee, v2._donnee));
}
private:
traits _t;
T _donnee;
public:
Variable(T);
Variable(const Variable<T, traits>&);
~Variable();

Variable<T, traits>& operator=(const Variable<T, traits>&);
Variable<T, traits>& operator=(const T&);

T& valeur()const;
};

Voici l'implémentation:

template<class T, class traits>
Variable<T, traits>::Variable(T donnee){
_t.assign(_donnee, donnee);
}

template<class T, class traits>
Variable<T, traits>::Variable(Variable<T, traits>& v){
_t.assign(_donnee, v._donnee);
}

template<class T, class traits>
Variable<T, traits>::~Variable(){
_t.free(_donnee);
}

template<class T, class traits>
Variable<T, traits>& Variable<T, traits>::operator=(const Variable<T, traits>& v){
_t.assign(_donnee, v._donnee);
}

template<class T, class traits>
Variable<T, traits>& Variable<T, traits>::operator=(const T& donnee){
_t.assign(_donnee, donnee);
}

template<class T, class traits>
T& Variable<T, traits>::valeur()const{
return _donnee;
}

Un simple petit test démontre que maintenant, avec le type char*, notre classe Variable fonctionne !

Imaginons que nous ajoutions une classe Date de notre cru.

class Date{
private:
int _jour;
int _mois;
int _annee;
public:
Date();
Date(int, int, int);
Date(const Date&);
~Date();
int getJour()const;
int getMois()const;
int getAnnee()const;
void setJour(int);
void setMois(int);
void setAnnee(int);
Date& operator=(const Date&);
};

Date::Date()
:_jour(1), _mois(1), _annee(1901){

}

Date::Date(int jour, int mois, int annee)
:_jour(jour), _mois(mois), _annee(annee){

}

Date::~Date{

}

int Date::getJour()const{
return _jour;
}

int Date::getMois()const{
return _mois;
}

int Date::getAnnee()const{
return _annee;
}

void Date::setJour(int jour){
_jour=jour;
}

void Date::setMois(int mois){
_mois=mois;
}

void Date::setAnnee(int annee){
_annee=annee;
}

Date& Date::operator=(const Date& d){
_jour=d._jour;
_mois=d._mois;
_annee=d._annee;
}

Nous pouvons à nouveau spécialiser la struct Trait pour les dates:

template<>
bool Trait<Date>::eq(Date d1, Date d2){
return ((d1.getJour()==d2.getJour())&&(d1.getMois()==d2.getMois())&&(d1.getAnnee()==d2.getAnnee()));
}

Et ainsi de suite pour les autres opérateurs.

Il existe d'autres nombreuses possibilités, à vous de les explorer.

L'exemple fourni dans le ZIP est un peu plus complet, mais il peut être amélioré et complété.
Il fournit l'exemple d'une pile pouvant contenir les différents types décrits dans cet article.
Bonne chance !

Codes Sources

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.