OVH Cloud OVH Cloud

ostream et cout

8 réponses
Avatar
ben64
Bonjour,

Excusez moi par avance pour cette question, mais je suis un débutant en
C++. J'espère qu'elle ne sera pas quand même trop ridicule. Je voudrais
faire une classe me permettant de logguer des messages, un peu comme
pourrait le faire cout. Je verrais bien la chose de cette façon (en
simplifiant) :

log << debug1 << "Message de DEBUG de niveau 1" << endl;
log << critical << "Message Critiques" << endl;
log << debug2 << Message de DEBUG de niveau 2" << endl;

Suivant la configuration de l'objet log, les messages de tel ou tel
niveau de debug ne serait pas pris en compte. Et en changeant la
configuration de l'objet on aurait des messages plus ou moins verbeux
(certains messages comme les erreurs critiques apparaissant dans tous
les cas de figure). Certains message pourraient être affichés sur la
sortie standard, d'autres sur la sortie d'erreur, d'autres écrits dans
une fichier, d'autres envoyés sur le réseau par une socket, ... toutes
les possibilités sont permises, mais la syntaxe reste la même et la
façon de logguer les messages est à la charge de l'objet log et pas de
l'application.

Ma première idée a été de faire une classe et d'implémenter l'opérateur
<<. Cependant je suis obligé de définir cet opérateur pour une chaine,
un entier, un float, ... et je ne profite pas de tous les manipulateurs
tels que endl, hex, ... Ce qui est génant et contraire au principe objet
de réutilisation. Ma deuxième idée a été de faire une classe qui hérite
de ostream. Mais je dois être trop mauvais pour y arriver :), car ca ne
marche pas. Si quelqu'un avait a une idée je suis preneur :)
Ma deuxième question et qui découle de la première, est à propos de
cout. Je ne vois pas du tout comment celui est implémenté. Est ce un
objet de la classe ostream ? J'ai l'impression que pour résoudre mon
problème j'ai juste besoin d'un cout, un peu plus évolué mais je ne vois
pas du tout comment faire.

Merci d'avance,

ben64

8 réponses

Avatar
Fabien LE LEZ
On Mon, 20 Dec 2004 23:09:10 +0100, ben64 :

Cependant je suis obligé de définir cet opérateur pour une chaine,
un entier, un float, ...


C'est justement à ça que ça sert, les templates...

--
;-)

Avatar
ben64
Fabien LE LEZ wrote:
On Mon, 20 Dec 2004 23:09:10 +0100, ben64 :


Cependant je suis obligé de définir cet opérateur pour une chaine,
un entier, un float, ...



C'est justement à ça que ça sert, les templates...



Bin pour moi la solution template est une mauvaise solution. Adieu
l'existence de tous les manipulateurs ?


Avatar
drkm
ben64 writes:

Bin pour moi la solution template est une mauvaise solution.


Pourquoi ?

--drkm

Avatar
SerGioGio
Je ne suis pas un spécialiste des stream, mais j'ai lu à plusieurs reprises
qu'il ne fallait pas de maniere generale dériver de ostream, istream et
compagnie, mais plutôt de streambuf. Par ailleurs créer une nouvelle classe
et redéfinir tous operateurs << (même en utilisant des templates), c'est un
peu réinventer la roue, et comme tu le notes tu perds entre autres les
manipulateurs. Je me rappelle l'avoir tenté à plusieurs reprises mais je
n'ai jamais éte satisfait par cette méthode.

Je vois trois pistes de solutions:

1. la plus simple est:
- une classe logger configurable, avec toutes les options de filtrage que tu
veux.
- une fonction membre log qui renvoie un ostream.

enum { debug1, debug2, critical } debug_level;
class logger
{
std::ofstream file_stream;
...
std::ostream& operator<<(debug_level a_debug_level) {
switch (a_debug_level) {
case debug1: return std::cout;
case debug2: return std::cerr;
case critical: return file_stream;
}
}
};

apres tu peux ecrire:
monLogger << critical << message1 << std::endl;

Par contre tu ne pourras pas écrire:
monLogger << debug1 << message1 << debug2 << message2 << std::endl;

2. une plus hardue
- une classe logger dérivant de ostream, avec toutes les options de filtrage
que tu veux.
- de nouveaux manipulateurs
On a toujours déconseillé de dériver de std::ostream, mais plutot de
std::streambuf... je ne vois pas ce quíl y a de mal dans cette utilisation
cependant (any guru?):

class logger : public std::ostream
{
std::ofstream file_stream;
...
logger : std::ostream(std::cout.rdbuf()) : {}
};

std::ostream& debug1(std::ostream& a_stream)
{
logger l = (logger&) a_stream;
l.rdbuf(std::cout.rdbuf());
return l;
}
std::ostream& debug2(std::ostream& a_stream)
{
logger l = (logger&) a_stream;
l.rdbuf(std::cerr.rdbuf());
return l;
}
std::ostream& critical(std::ostream& a_stream)
{
logger l = (logger&) a_stream;
l.rdbuf(l.file_stream.rdbuf());
return l;
}

(Attention j'ai fait abstraction ici des problemes d'acces public/prive...)
Et la tu peux écrire:
monLogger << debug1 << message1 << debug2 << message2 << std::endl;

L'intéret est limité, et il y a d'affreux casts. Par ailleurs,
std::cout << critical;
va compiler mais planter...
Désoolé cependant mais je ne sais pas faire sans ces inconvénients...

3. La dernier piste est de reprendre presque exactement la solution 2 avec
pour seule nuance dériver de std::streambuf au lieu de std::ostream;
Mais ce sera encore plus compliqué et les defauts seront les memes.
Pour dériver streambuf, un bon exemple d'un gourou:
http://groups.google.com/groups?hl=fr&lr=&selm€f2i5%246kn%241%40nnrp1.deja.com&rnum=6

Enfin, des considérations générales:
- pour ne rien écrire dans le logger, il a été suggéré dans ce forum
d'utiliser un ofstream non initialisé:
std::ofstream() << "Hello world" << std::endl;
ou bien de créer un "nulstreambuf"...
- pour écrire sur une socket, il y a des classes qui dérivent de streambuf
qui permettent de le faire en utilisant des objets ostream. voir par exemple
socket++ (que je n'ai jamais utilisé).

SerGioGio



"ben64" a écrit dans le message de
news:41c74cfd$0$9545$
Bonjour,

Excusez moi par avance pour cette question, mais je suis un débutant en
C++. J'espère qu'elle ne sera pas quand même trop ridicule. Je voudrais
faire une classe me permettant de logguer des messages, un peu comme
pourrait le faire cout. Je verrais bien la chose de cette façon (en
simplifiant) :

log << debug1 << "Message de DEBUG de niveau 1" << endl;
log << critical << "Message Critiques" << endl;
log << debug2 << Message de DEBUG de niveau 2" << endl;

Suivant la configuration de l'objet log, les messages de tel ou tel
niveau de debug ne serait pas pris en compte. Et en changeant la
configuration de l'objet on aurait des messages plus ou moins verbeux
(certains messages comme les erreurs critiques apparaissant dans tous
les cas de figure). Certains message pourraient être affichés sur la
sortie standard, d'autres sur la sortie d'erreur, d'autres écrits dans
une fichier, d'autres envoyés sur le réseau par une socket, ... toutes
les possibilités sont permises, mais la syntaxe reste la même et la
façon de logguer les messages est à la charge de l'objet log et pas de
l'application.

Ma première idée a été de faire une classe et d'implémenter l'opérateur
<<. Cependant je suis obligé de définir cet opérateur pour une chaine,
un entier, un float, ... et je ne profite pas de tous les manipulateurs
tels que endl, hex, ... Ce qui est génant et contraire au principe objet
de réutilisation. Ma deuxième idée a été de faire une classe qui hérite
de ostream. Mais je dois être trop mauvais pour y arriver :), car ca ne
marche pas. Si quelqu'un avait a une idée je suis preneur :)
Ma deuxième question et qui découle de la première, est à propos de
cout. Je ne vois pas du tout comment celui est implémenté. Est ce un
objet de la classe ostream ? J'ai l'impression que pour résoudre mon
problème j'ai juste besoin d'un cout, un peu plus évolué mais je ne vois
pas du tout comment faire.

Merci d'avance,

ben64


Avatar
Loïc Joly
ben64 wrote:
Bonjour,

Excusez moi par avance pour cette question, mais je suis un débutant
en C++. J'espère qu'elle ne sera pas quand même trop ridicule. Je
voudrais faire une classe me permettant de logguer des messages, un peu
comme pourrait le faire cout. Je verrais bien la chose de cette façon
(en simplifiant) :


Si tu vas dans la liste des fichiers temporaires de boost, tu trouveras
une bibliothèque de logging qui fait tout plein de choses.

--
Loïc

Avatar
kanze
ben64 wrote:

Excusez moi par avance pour cette question, mais je suis un
débutant

en C++. J'espère qu'elle ne sera pas quand même trop ridicule. Je
voudrais faire une classe me permettant de logguer des messages, un
peu comme pourrait le faire cout. Je verrais bien la chose de cette
façon (en simplifiant) :

log << debug1 << "Message de DEBUG de niveau 1" << endl;
log << critical << "Message Critiques" << endl;
log << debug2 << Message de DEBUG de niveau 2" << endl;

Suivant la configuration de l'objet log, les messages de tel ou tel
niveau de debug ne serait pas pris en compte. Et en changeant la
configuration de l'objet on aurait des messages plus ou moins verbeux
(certains messages comme les erreurs critiques apparaissant dans tous
les cas de figure). Certains message pourraient être affichés sur
la

sortie standard, d'autres sur la sortie d'erreur, d'autres écrits
dans

une fichier, d'autres envoyés sur le réseau par une socket, ...
toutes

les possibilités sont permises, mais la syntaxe reste la même et la
façon de logguer les messages est à la charge de l'objet log et pas
de

l'application.

Ma première idée a été de faire une classe et d'implémenter
l'opérateur <<. Cependant je suis obligé de définir cet opérateur
pour

une chaine, un entier, un float, ... et je ne profite pas de tous les
manipulateurs tels que endl, hex, ... Ce qui est génant et contraire
au principe objet de réutilisation. Ma deuxième idée a été de
faire

une classe qui hérite de ostream. Mais je dois être trop mauvais
pour

y arriver :), car ca ne marche pas. Si quelqu'un avait a une idée je
suis preneur :)


En ce qui concerne l'opérateur <<, il n'y a pas de problème. Dans ta
classe, tu vas bien te servir des flux. (Je te montrerai comment
ci-dessus.) Tu aurais un flux (std::ostream) par niveau. Ces flux
seront
emballés dans un LogStream, et tu définis la fonction templatée
suivante :

template< typename T >
LogStream&
operator<<( LogStream& dest, T const& valeur )
{
std::ostream* flux = dest.getStream() ;
if ( flux != NULL ) {
*flux << valeur ;
}
return dest ;
}

Ma deuxième question et qui découle de la première, est à propos
de

cout. Je ne vois pas du tout comment celui est implémenté. Est ce
un

objet de la classe ostream ? J'ai l'impression que pour résoudre mon
problème j'ai juste besoin d'un cout, un peu plus évolué mais je
ne

vois pas du tout comment faire.


Ben, d'abord, il faut comprendre comment est implémenté std::ostream.
En
gros, l'ostream même ne s'occupe que du formattage. Il délégue la
sortie
des caractères générés à un std::streambuf. Et c'est là où tu
pourras
intervenir -- il y a un constructeur de ostream qui prend streambuf*,
et
std::streambuf, c'est plein des fonctions virtuelles qui te permettent
toutes les libertés. Donc, pour sortir vers un fichier, il existe une
classe dérivée std::filebuf, pour rétrouver les sorties dans un
std::string, un std::stringbuf. Et pour filtrer des sorties avant des
les balancer à un streambuf quelconque, des streambuf filtrant que tu
pourrais écrire. (À cet égard, voir à mon site, www.gabi-soft.fr.
Il y a
une copie des articles sur les streambuf filtrants que j'ai écrit,
ainsi
que du code pour une charpente templaté qui en facilite l'écriture
dans
les cas simples.)

Dans le cas des logs, on se sert typiquement d'un streambuf spécial
avec
des fonctions en plus pour lui dire que c'est le début d'un
enrégistrement de log, et qu'on a fini avec l'enrégistrement du log.
Donc, au début, on lui passe aussi le nom du fichier et le numéro de
ligne, et peut-être un mot clé en ce qui concerne la séverité (voir
ci-dessous) -- il les sort en tête de la première ligne, et il
indente
les lignes suivantes. À la fin, il s'assure que l'enrégistrement est
bien terminé par un 'n', en ajoute si nécessaire, et fait un flush.
En
plus, il contient un tableau de streambuf* auxquels il envoie tous les
caractères -- si tu veux que le log va vers un fichier, tu mets un
filebuf dans ce tableau ; pour cerr, il faut utiliser le résultat de
std::cerr.rdbuf(), pour des email ou un syslog, un streambuf à toi qui
collectionne les caractères dans un std::string ou un
std::vector<char>,
et qui en fait ce qu'il faut dans le flush (function virtuelle sync()
du
streambuf).

Dans la pratique, en général, j'ai un objet global log de type
LogManager, qui maintient un hièrarchie de streambuf par niveau de log
(c-à-d debug, info, error, etc.) ; les hièrarchies sont créées lors
de
la configuration du log. (Ça devient un peu compliqué lorsqu'on veut
modifier la configuration du log sans arrêter l'application dans un
contexte multi-threaded, mais sinon, ça ne doit pas posé de gros
problèmes.) J'ai donc un tableau de streambuf*, indicé par la
séverité,
avec un pointeur nul où il n'y a rien à faire, ou un pointeur à une
instance d'un streambuf comme décrit ci-dessus, qui lui contient des
pointeurs à des streambuf finaux.

Alors, quand tu veux un log, tu appelles une fonction avec comme
information la séverité, le nom du fichier et le numéro de la ligne.
En
général, on emballe cet appel dans un macro, afin de passer __FILE__
et
__LINE__ automatiquement, sans que le programmeur ait à son occuper.
Souvent, même, on a un macro différent par niveau de séverité ; le
programmeur utilisateur se contente d'écrire « DEBUG << ... » etc.
Cette
fonction régarde dans le tableau des streambuf, et renvoie un
LogStream
soit sans ostream, soit avec un ostream initialisé avec le pointeur de
streambuf pris du tableau. (On pourrait maintenir un tableau de
ostream, plutôt que des streambuf, pour éviter de construire un
ostream
chaque fois. Personnellement, je préfère créer le ostream chaque
fois --
comme ça, je suis assuré que tous les options de formattage ont leur
état par défaut, indépendamment de ce qui s'est passé dans le log
précédant. Ça ne coûte que si le log est actif, et dans ce cas-là,
il va
y avoir de toute façon des formattages et des sorties, qui coûteront
probablement beaucoup plus.)

Évidemment, c'est quand on crée le LogStream qu'on appelle aussi la
fonction du streambuf pour initialiser un enrégistrement de log.

Le seul truc ici, c'est de donner au wrapper une sémantique de valeur,
afin qu'on puisse le retourner par valeur, et que la durée de vie des
temporaires se charge d'appeler le destructeur (parce que c'est dans le
destructeur de la dernière copie qu'on va appeler la fonction de fin
d'enrégistrement de log dont j'ai parlé tout à l'heure). Ça suppose
un
espèce de comptage de réféfences.

Dans un environement multi-threaded :

1. Si le log pour le sévérité donnée est complètement
désactivé, on ne
modifie rien, et on n'accède à rien qu'un autre thread risque de
modifie. On n'a donc pas besoin de lock.

2. Si le log est actif, on prend le lock avant de faire quoique ce
soit, et on le libère dans le dernier destructeur du LogStream.

Note qu'il vaut la peine de prêter un peu d'attention aux performances
quand le log est desactivé. Donc, dans mon cas, les macros comme DEBUG
se résolvent à un appel « LogManager::log( Log::debug, __FILE__,
__LINE__ ) », qui est une fonction inline à peu près :

LogStream
LogManager::log(
Log::Severity severity,
char const* filename,
int lineNumber )
{
std::streambuf* sb = ourStreambufTable[ severity ] ;
return sb == NULL
? ourInactiveLogStream
: LogStream( sb, severity, filename, lineNumber ) ;
}

Toute la reste de la logique se trouve dans le constructeur de
LogStream, qui ne serait appelé que si le log est activé.

Il y a pas mal de code pour ce genre de choses à ma site, dans le
sous-système Util/IO : voir par exemple FilteringOutputStream (mais
les
streambuf ici sont assez compliqué que les templates ne serviront
pas),
OutputStreamWrapper (qui gère justement cette histoire de comptage de
références), et EventGeneratingOutputStream (qui est un streambuf
filtrant qui génère des callback sur certains évenemments, comme
début
ou fin de ligne).

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Avatar
drkm
"SerGioGio" writes:

std::ostream& critical(std::ostream& a_stream)
{
logger l = (logger&) a_stream;
l.rdbuf(l.file_stream.rdbuf());
return l;
}


Pourquoi ne pas passer directement un « logger » ?

--drkm

Avatar
drkm
Loïc Joly writes:

Si tu vas dans la liste des fichiers temporaires de boost, tu trouveras
une bibliothèque de logging qui fait tout plein de choses.


Elle n'a donc toujours pas été acceptée ? L'utilises-tu ? A-t-elle
des avantages sur d'autres solutions existantes, comme log4cxx ?

--drkm