© D. Mathieu
mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique
Créé le 19/01/2000 -
Dernière mise à jour : 26/02/2001
De nombreux manipulateurs standard sont mis à la disposition des utilisateurs des flux pour faciliter le formattage des E/S.
ostream & endl (ostream &); // insert newline
and flush
ostream & ends (ostream &); // insert null to terminate string ostream & flush (ostream &); // flush the ostream ios & dec (ios &); // set conversion base to decimal ios & hex (ios &); // set conversion base to hexadécimal ios & oct (ios &); // set conversion base to octal |
On peut donc écrire :
cout << hex << 12 << endl; |
Dans le document "Fonctions de gestion des dates et du temps" est présentée une fonction
Estampiller()
qui affiche la date courante dans le flux qui lui est passé en paramètre.
Elle est difficile à utiliser du fait qu'elle ne peut être insérée dans une suite d'injections.
Elle devient beaucoup plus élégante si elle est transformée en un manipulateur : il suffit pour cela de lui faire renvoyer le flux qui lui est passé en paramètre :
ostream & Estampiller (ostream & os)
{ std::time_t t = std::time (0); return os << "date : " << std::asctime (std::localtime (&t)); } // Estampiller() |
et de l'utiliser de la façon suivante :
#include <iomanip>
cerr << "####" << Estampiller << "----" << endl; //
ligne {1}
|
qui provoque l'affichage suivant :
####date : Mon Feb 12 21:20:40 2001
---- [1] ####date : Mon Feb 12 21:20:40 2001 ---- |
Il faut tout d'abord remarquer que, dans la ligne [1] ci-dessus, il ne s'agit pas du tout de l'appel de la fonction Estampiller (qui aurait été écrit Estampiller(cerr)). Rappelons aussi que l'identificateur d'une fonction, sans les (), est un pointeur vers cette fonction (c'est-à-dire qu'il représente l'adresse à laquelle est le code de la fonction).
Le second opérande Estampiller (opérande droit) du second opérateur << de la
ligne [1]
est donc un pointeur vers une fonction qui a pour paramètre un flux ostream passé par référence et qui renvoie aussi la référence d'un flux ostream.
En C++, le type de cet opérante s'écrit :
ostream & (*) (ostream &) |
Il faut donc qu'il existe quelque part une surcharge de l'opérateur << ayant pour profil :
ostream & operator << (ostream &, ostream & (*) (ostream &)); |
Cette surcharge existe dans le fichier <iomanip>, dont la définition pourrait être :
ostream & operator << (ostream & os, ostream & (*f) (ostream &))
{ return f (os); } |
Si la fonction nécessite un paramètre supplémentaire, le problème devient beaucoup plus complexe.
Considérons par exemple le manipulateur Image qui affiche les chaînes true ou false selon la valeur d'un booléen (l'injecteur << affiche 1 ou 0) qui lui est passé en paramètre.
Par analogie avec le manipulateur Estampiller(), on pourrait l'écrire ;
ostream & Image (ostream & os, bool Val)
{ return os << (Val ? "true" : "false"); } // Image() |
Il serait aussi nécessaire de surcharger de nouveau l'opérateur << ainsi :
ostream & operator << (ostream & os, ostream & (*) (ostream &, bool)); |
Il serait de toutes façons impossible d'utiliser cette fonction en lui passant un paramètre booléen sans que ce soit un appel, aucune des deux écritures ci-dessous n'étant valide :
cout << Image << (1 == 3) << endl;
// le booléen n'est pas passé en paramètre
cout << Image (1 == 3) << endl; // la fonction est évaluée, et son résultat est injecté dans le flux |
La solution consiste à créer une classe CImage, telle que CImage (3) représente le constructeur d'un objet, et que l'objet ainsi construit puisse être injecté dans un flux.
Cela revient à dire soit que la classe CImage est CEditable, soit que l'opérateur injecteur << est surchargé pour cette classe.
On peut écrire par exemple :
class CImage
{ bool m_Bool; public : CImage (bool Bool) : m_Bool (Bool) {} friend ostream & operator << (ostream &, const CImage & Val); }; // CImage ostream & operator << (ostream & os, const CImage & Val)
} // operator << |
et :
cout << "1 == 3 : " << CImage (1 == 3) << endl; // 1 == 3 : false
cout << "2 == 2 : " << CImage (2 == 2) << endl; // 2 == 2 : true |
Cette technique, qui consiste à faire effectuer l'affichage par un objet d'une classe pour laquelle l'opérateur << est surchargé, est attribuée par B. Eckel à J. Schwartz. Un tel manipulateur est appelé un effector.
L'exemple ci-dessous montre un manipulateur qui affiche en binaire un entier non signé :
class CBin
{ unsigned m_Unsigned; public : CBin (unsigned Unsigned) : m_Unsigned (Unsigned) {} friend ostream & operator << (ostream &, const CBin & Val); }; // CBin ostream & operator << (ostream & os, const CBin & Val)
} // operator << |
et qui peut être utilisé ainsi :
cout << "0xFFFFFFFF = " << CBin (0xFFFFFFFF) << '\n'
<< "0xF0F0F0F0 = " << CBin (0xF0F0F0F0) << '\n' << "0xOOOOOOOF = " << CBin (0x0000000F) << '\n' << "0xOOOOOOO0 = " << CBin (0x00000000) << endl; |
Le manipulateur présenté ici permet d'afficher entre {} la valeur d'un objet quelconque, pourvu que cette valeur soit affichable au moyen de l'injecteur.
Il a la même structure que CImage ci-dessus, mais le type bool est remplacé par un type générique :
template <typename T> class CEntreAccolades
{ T m_Info; public : CEntreAccolades (const T & Info) : m_Info (Info) {}
friend ostream & operator << (ostream & os, const CEntreAccolades & Val)
}; // CEntreAccolades |
qui peut être utilisé ainsi :
cout << CEntreAccolades <int> (3) << "; "
<< CEntreAccolades <int, '<', '>'> (-12) << endl; |
A l'évidence, la technique exposée ci-dessus n'est pas très commode à utiliser, chaque manipulateur devant être instancié avant d'être utilisé.
Rappelons que, contrairement aux classes génériques qui doivent être explicitement instanciées avant leur utilisation, les fonctions génériques sont automatiquement instanciées par le compilateur lors de leur utilisation
Il convient alors d'utiliser une très élégante solution qui passe par l'écriture d'une fonction qui construit elle-même l'objet nécessaitre :
template <typename T> CEntreAccolades <T> makeEntreAccolades (const T & Info)
{ return CEntreAccolades <T> (Info); } // makeEntreAccolades() |
et :
cout << makeEntreAccolades (1234) : << endl;
<< makeEntreAccolades ("azerty") << endl; |
ostream & uint2bin (ostream & os, const unsigned & Val); |
Non seulement le manipulateur doit stocker la valeur à traiter, mais aussi la fonction à effectuer lors de l'appel de l'injecteur.
Il doit donc avoir la forme suivante :
class Cuint2bin
{ private : ostream & (*m_Traitement) (ostream &, const unsigned &); unsigned m_Param;
public :
}; // Cuint2bin |
Il suffit maintenant d'écrire une fonction qui crée un manipulateur de ce type :
Cuint2bin Bin (unsigned uint) { return Cuint2bin (uint2bin, uint); } |
et de l'utiliser le plus simplement :
cout << "0xFFFFFFFF = " << Bin (0xFFFFFFFF) << '\n'
<< "0xF0F0F0F0 = " << Bin (0xF0F0F0F0) << '\n' << "0xOOOOOOOF = " << Bin (0x0000000F) << '\n' << "0xOOOOOOO0 = " << Bin (0x00000000) << endl; |
La fonction uint2bin() pourrait ùtre écrite :
ostream & uint2bin (ostream & os, const unsigned & Val)
{ for (unsigned Mask = 1 << (8 * sizeof (unsigned) - 1); Mask; ) { os << (Mask & Val ? '1' : '0'); Mask >>= 1; } return os; } // uint2bin() |
Le manipulateur ignore tout de la fonction qu'il appliquera lors de l'injection.
Il peut aussi tout ignorer du paramètre qu'il traite : il suffit de le rendre générique.
Stroustrup définit ainsi un manipulateur générique :
template <class T> class COmanip
{ private : ostream & (*m_Traitement) (ostream &, const T &); T m_Param;
public :
friend ostream & operator << (ostream & os, const COmanip & m)
}; // COmanip |
L'utilisation d'un tel manipulateur pour afficher des valeurs entre crochets, nécessite de plus :
template <class T>
ostream & Crochets (ostream & os, const T & i) { return os << '[' << i << ']'; } // Crochets() template <typename T>
} // ManipCrochets() |
et :
cout << ManipCrochets (12) << endl;
cout << ManipCrochets (12.34) << endl; |
Une classe analogue à la classe COManip, la classe smanip, existe dans la norme. Celle-ci n'impose pas son implémentation.
Elle est accessible par le fichier <iomanip>.
Le compilateur propose plusieurs autres manipulateurs génériques dans le fichier /usr/include/g++-2/iomanip.h>.
template <class T> class imanip;
template <c1ass T> class omanip; template <class T> class iapp; template <class T> class oapp; |
Plusieurs des manipulateurs standard sont implémentés au moyen de ces classes génériques.
© D. Mathieu
mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique