©
D. Mathieu
mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique
Créé le 04/08/2000 -
Dernière mise à jour : 06/02/2001
En C++, le même mécanisme n'est pas automatique mais peut être aisément mis en place, en créant une classe générale virtuelle CObject, abstraite ou pas, qui pourrait être écrite et utilisée ainsi :
class CObject
{ ... public : virtual string toString (void) const { return string (); } }; // CObject
virtual string toString (void) const;
|
string CRationnel::toString (void) const
{ const unsigned CstSize = 128; char Tab [CstSize];
ostr << m_Num << '/' << m_Denom << ends; return Tab; }; // toString() |
void Editer (const CObject & Obj)
{ cout << Obj.toString(); // Utilisation du polymorphisme } // Editer() |
Editer (CRationnel ( 1, 3));
cout << endl; CRationnel R1 (12, 23);
|
Le principal inconvénient de cette méthode est la création d'une chaîne intermédiaire, quelle qu'en soit la destination ultérieure, même si elle est immédiatement éditée dans un flux et plus jamais utilisée. Une première solution est de rendre la classe CRationnel "éditable", soit en surchargeant l'opérateur injecteur <<, soit en la faisant dériver d'une classe virtuelle CEditable. Une autre solution élégante offerte par le C++, et très rarement utilisée, est la construction d'un manipulateur.
class CRationnel
{ protected : int m_Num; int m_Denom; ... public : CRationnel (const int Num = 0, const int Denom = 1); friend ostream & operator << (ostream
& os, const CRationnel & R);
|
ostream & operator << (ostream & os, const
CRationnel & R)
{ return os << R.m_Num << '/' << R.m_Denom; } // operator <<() |
cout << CRationnel (123, 45) << endl;
cerr << CRationnel ( 3, 1) << endl; |
Le principal inconvénient de cette méthode est de ne pas permettre le polymorphisme.
Chaque classe d'une hiérarchie doit avoir son propre opérateur << surchargé, et c'est celui de la classe indiquée qui est appelé, et non celui de la classe réelle, l'opérateur << n'étant pas une fonction virtuelle de la classe.
Considérons l'exemple suivant d'une classe CRationnelInf1, pour laquelle le numérateur est toujours inférieur ou égal au dénominateur, et dont l'injecteur est surchargé pour afficher la valeur réelle du rationnel :
class CRationnelInf1 : public CRationnel
{ ... public : ... friend ostream & operator << (ostream & os, const CRationnel & R); ... }; // CRationnelInf1 |
ostream & operator << (ostream & os, const
CRationnelInf1 & R)
{ return os << (static_cast <float> (R.m_Num) / static_cast <float> (R.m_Denom)); } // operator << () |
void EditerRationnel (const CRationnel & Obj)
{ cout << Obj << endl; } // EditerRationnel() |
Comme le montre la séquence suivante, l'affichage obtenu par la fonction EditerRationnel() n'a pas la présentation voulue, le polymorphisme n'étant pas disponible :
// Affichage de :
cout << CRationnelInf1 (12, 24) << endl; // 0.5 cerr << CRationnelInf1 (12, 36) << endl; // 0.333333 EditerRationnel (CRationnel (12, 24)); // 12/24 EditerRationnel (CRationnelInf1 (12, 36)); // 12/36 au lieu de 0.333333 |
Rien n'empêche d'utiliser la surcharge de << pour transformer un rationnel en chaîne de caractères, grâce aux "flux-chaînes" du C++, mais l'écriture est un peu plus lourde et surtout, doit être recommencée à chaque utilisation :
const unsigned CstSize = 128;
char Tab [CstSize];
cout << Tab << endl; |
Remarque : la surcharge de << n'interdit en rien la transformation en chaîne de caractères, en faisant aussi dériver la classe CRationnel de CObject.
class CEditable
{ protected : virtual ostream & _Edit [1] (ostream & os) const = 0; public :
}; // CEditable |
inline ostream & operator << (ostream & os,
const CEditable & Obj)
{ return Obj._Edit (os); } // operator << |
class CRationnel : public CEditable
{ protected : int m_Num; int m_Denom; virtual ostream & _Edit (ostream &
os) const
} // _Edit();
...
|
class CRationnelInf1 : public CRationnel
{ ... protected : virtual ostream & _Edit (ostream & os) const { return os << (static_cast <float> (R.m_Num) / static_cast <float> (R.m_Denom)); } // _Edit() ... }; // CRationnelInf1 |
void EditerRationnel (const CRationnel & Obj)
{ cout << Obj << endl; } // EditerRationnel() |
Cette fois, la même séquence que précédemment montre que le polymorphisme est mis en oeuvre :
// Affichage de :
cout << CRationnelInf1 (12, 24) << endl; // 0.5 cerr << CRationnelInf1 (12, 36) << endl; // 0.333333 EditerRationnel (CRationnel (12, 24)); // 12/24 EditerRationnel (CRationnelInf1 (12, 36)); // 0.333333 |
class CIndividu : public CEditable
{ protected : CDate m_DNaissance; string m_Nom; string m_Prenom; virtual ostream & _Edit (ostream &
os) const
}; // CIndividu class CEmploye : public CIndividu
virtual ostream & _Edit (ostream &
os) const
}; // CEmploye |
Nous avons choisi ici une édition "récursive", en affichant toutes les classes, de la plus élevée hiérarchiquement à la plus basse. Ce n'est pas toujours nécessaire ni souhaitable, et si un objet contient par exemple une liste, il n'est pas forcément judicieux d'éditer tous les objets de cette liste.
class CRationnel : public CObject, public CEditable { ... }; |
#ifdef _DEBUG
... #endif |
CEmploye Toto (...);
cout << Toto << endl;
|
Une solution serait de différencier les flux, en créant une classe de flux CFluxDump, dérivée de ostream. Toute injection dans ce type de flux serait un "dump", toute autre injection serait une "édition" : voir [DEV99].
class CDate
{ int m_Jour; int m_Mois; int m_Annee; public : CDate (int Jour, int Mois, int Annee) : m_Jour (Jour), m_Mois (Mois), m_Annee (Annee) {} friend ostream & EditDate (ostream & os, const CDate & Date) { return os << Date.m_Jour << '-' << Date.m_Mois << '-' << Date.m_Annee; } // EditDate() }; // CDate COmanip } // Edit()
void Essai (void)
} // Essai()
|
[PPRAD99] | http://perso.club-internet.fr/pprados/Langage/Java/toString/toString.html |
[MSVC5] | voir la classe CDump de Visual C++ Microsoft |
[DEV99] | voir devoir système 1999-2000 - IUT Aix - Dépt Info |
[1] Nous avons fait précéder l'identificateur de la fonction _Edit() d'un caractère souligné, car il n'est pas improbable qu'un utilisateur, faisant dériver une de ses propres classe de CEditable, ait déjà une fonction Edit(), ce qui pourrait provoquer un conflit. Il est recommandé de ne pas utiliser les identificateurs précédés d'un ou deux caractères _ comme identificateurs "normaux".
© D. Mathieu
mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique