Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Surcharge de l'operator

16 réponses
Avatar
condo4
Bonjour,

Je souhaite cr=E9er une classe pour cr=E9er des log.

Class SysLog

j'aimerai utiliser comme ceci :

SysLog a;
a<<"Chaine a logguer : "<<45;

elle ecrira dans un fichier
2007/03/06 12:10:30 Chaine a logguer : 45

Mais je ne sais pas comment m'y prendre, qu'elle operateur
surcharger....

Pour le moment, j'ai essayer =E7a, mais =E7a ne marche pas....

Quelqu'un peut-il m'eclairer sur la bonne fa=E7on pour surcharger << ???

Merci.



class LogSys{
public:
ofstream logfile;
LogSys();
~LogSys();
ostream& operator<<(ostream& flux);
};


LogSys::LogSys(){
logfile.open("c:\\Trace.log",ios::app);
}

LogSys::~LogSys(){
logfile.close();
}

ostream& LogSys::operator<<(const ostream& flux){
char now[31];
time_t rawtime;
struct tm * timeinfo;
time(&rawtime);
timeinfo =3D localtime (&rawtime);
strftime(now,100,"%Y/%m/%d %H:%M:%S : ",timeinfo);
return logfile<<now<<flux<<endl;
}


@+Fab

10 réponses

1 2
Avatar
Jean-Marc Bourguet
"condo4" writes:

Bonjour,

Je souhaite créer une classe pour créer des log.

Class SysLog

j'aimerai utiliser comme ceci :

SysLog a;
a<<"Chaine a logguer : "<<45;


La technique habituelle consiste a creer ton propre streambuf. Un exemple
dans la FAQ. On doit pouvoir en trouver d'autres en faisant une recherche
dans les archives.

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org

Avatar
Michael DOUBEZ
Bonjour,

Je souhaite créer une classe pour créer des log.

Class SysLog

j'aimerai utiliser comme ceci :

SysLog a;
a<<"Chaine a logguer : "<<45;

elle ecrira dans un fichier
2007/03/06 12:10:30 Chaine a logguer : 45

Mais je ne sais pas comment m'y prendre, qu'elle operateur
surcharger....

Pour le moment, j'ai essayer ça, mais ça ne marche pas....

Quelqu'un peut-il m'eclairer sur la bonne façon pour surcharger << ???

Merci.



class LogSys{
public:
ofstream logfile;
LogSys();
~LogSys();
ostream& operator<<(ostream& flux);
};


LogSys::LogSys(){
logfile.open("c:Trace.log",ios::app);
}

LogSys::~LogSys(){
logfile.close();
}

ostream& LogSys::operator<<(const ostream& flux){
char now[31];
time_t rawtime;
struct tm * timeinfo;
time(&rawtime);
timeinfo = localtime (&rawtime);
strftime(now,100,"%Y/%m/%d %H:%M:%S : ",timeinfo);
return logfile<<now<<flux<<endl;
}


Le problème principal est de savoir quand débute et fini un log.

Le truc c'est de passez par un objet temporaire pour identifier quand le
log est terminé (i.e. à sa destruction).
Qq chose du type:
class LogSys
{
//...

//log single string
void logString(const string& str)
{
if(!str.empty())
{
//logfile<<date...
logfile<<str;
}
}

//method to getlogstream
logger stream(){return logger(*this);}

protected:
//logger is implemented in term of ostringstream
struct logger:public ostringstream
{
LogSys& owner;
logger(LogSys& o):owner(o){}
logger(logger& l):ostringstream(l.str()),owner(l.owner){}
~logger()
{//envoi le log à LogSys
this->owner.logString(this->str());}
}
};


Et ensuite:
SysLog a;
a.stream()<<"Chaine a logguer : "<<45;

Tu peux même passer des paramètres (niveau de log ...), à stream() qui
seront délégués à LogSys::logString.

Michael

Avatar
Michael DOUBEZ
"condo4" writes:

Bonjour,

Je souhaite créer une classe pour créer des log.

Class SysLog

j'aimerai utiliser comme ceci :

SysLog a;
a<<"Chaine a logguer : "<<45;


La technique habituelle consiste a creer ton propre streambuf. Un exemple
dans la FAQ. On doit pouvoir en trouver d'autres en faisant une recherche
dans les archives.



En général mais si il crée son streambuf il va avoir un log du type
2007-03-06 13:20:42 - Chaine a logguer :
2007-03-06 13:20:42 - 45

or il veut
2007-03-06 13:20:42 - Chaine a logguer : 45

Ici, il faut donc passer par une technique du genre RAII (cf mon autre
post) ou alors une stream à état:
a.logbegin();
a<<...;
a.logend();
Ce qui pose d'autres problèmes (pas pratique en environement multi
thread, design a état pas pratique).

Michael


Avatar
Jean-Marc Bourguet
Michael DOUBEZ writes:

"condo4" writes:

Bonjour,

Je souhaite créer une classe pour créer des log.

Class SysLog

j'aimerai utiliser comme ceci :

SysLog a;
a<<"Chaine a logguer : "<<45;
La technique habituelle consiste a creer ton propre streambuf. Un exemple

dans la FAQ. On doit pouvoir en trouver d'autres en faisant une recherche
dans les archives.



En général mais si il crée son streambuf il va avoir un log du type
2007-03-06 13:20:42 - Chaine a logguer :
2007-03-06 13:20:42 - 45


Il faut naturellement choisir ou on veut la chaine d'identification.
Quelques solutions:
- a chaque ligne
- a chaque flush
- avec un formatteur special.

Ici, il faut donc passer par une technique du genre RAII (cf mon autre
post)


Aussi. Les deux premieres solutions si elles sont applicables ont
l'avantage de mieux fonctionner avec l'utilisation de la stream dans des
contextes plus elabore (si on la passe a une fonction par exemple).

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org



Avatar
condo4
et est-ce qu'il ne serait pas possible d'heriter de ofstream ?
Du style :
using namespace std;

class LogSys: public ofstream{
public:
__ostream_type & operator<< (__streambuf_type *__sb);
};

__ostream_type & LogSys::operator<< (__streambuf_type *__sb){
char now[31];
time_t rawtime;
struct tm * timeinfo;
time(&rawtime);
timeinfo = localtime (&rawtime);
strftime(now,100,"%Y/%m/%d %H:%M:%S : ",timeinfo);


//Et la on appel la fonction de l'ancetre avec now devant __sb ????


}
Avatar
James Kanze
Michael DOUBEZ wrote:
"condo4" writes:

Je souhaite créer une classe pour créer des log.

Class SysLog

j'aimerai utiliser comme ceci :

SysLog a;
a<<"Chaine a logguer : "<<45;


La technique habituelle consiste a creer ton propre streambuf. Un exem ple
dans la FAQ. On doit pouvoir en trouver d'autres en faisant une recher che
dans les archives.



En effet. L'exemple dans
http://kanze.james.neuf.fr/code/Util/IO/FilteringOutputStream/examples/gb/T imeStampInserter.hh
Il sert avec les FilteringOutputStream dans le répertoire
en-dessous -- plus généralement, voir
http://kanze.james.neuf.fr/code-fr.html pour les détails, ainsi
que mes articles sur le sujet dans
http://kanze.james.neuf.fr/articles-fr.html. (Les articles mêmes
sont en anglais.)

En général mais si il crée son streambuf il va avoir un log du type
2007-03-06 13:20:42 - Chaine a logguer :
2007-03-06 13:20:42 - 45


Comment ça ? On ne pourrait pas faire ça même si on voulait. Le
streambuf ne connaît pas les opérateurs <<. Il ne connaît que
les caractères sortis.

or il veut
2007-03-06 13:20:42 - Chaine a logguer : 45


Ce qui est exactement ce que fait le streambuf filtrant
ci-dessus.

Ici, il faut donc passer par une technique du genre RAII (cf mon autre
post) ou alors une stream à état:
a.logbegin();
a<<...;
a.logend();
Ce qui pose d'autres problèmes (pas pratique en environement multi
thread, design a état pas pratique).


Tout dépend. Si on veut bien séparer les enrégistrements
autrement que par un caractère donné (genre 'n'), il faut
passer par un interméiaire qui notifie le streambuf. Pour cela,
j'utilise en général un wrapper, comme le OutputStreamWrapper,
disponible au même site, en collaboration avec un streambuf
spécial -- le streambuf (ou l'inserter, si on se base sur le
streambuf filtrant ci-dessus) a deux fonctions supplémentaire :
startRecord et endRecord, qui donnent les tops, pour ainsi dire.
Avec l'infrastructure dans ma bibliothèque ci-dessus, quelque
chose comme le suivant doit faire l'affaire :

class LogInserter : public Gabi::SimpleInserter
{
public:
explicit LogInserter(
std::string const& format
= "%Y-%m-%d %H:%M:%S: " )
: myState( inactive )
, myFormat( format )
{
}
int insert( std::streambuf& dest, int ch )
{
int result = EOF ;
if ( myState != inactive ) {
if ( myState == atStartOfLine ) {
dest.sputn( myHeader.data(), myHeader.size() ) ;
myHeader = " " ;
}
ch = dest.sputc( ch ) ;
myState = (ch == 'n' ? atStartOfLine : inLine) ;
}
myOwner = &dest ;
return result ;
}

void startRecord()
{
assert( myState == inactive ) ;
time_t now = time( NULL ) ;
tm t = *localtime( &now ) ;
char buffer[ 1000 ] ;
if ( strftime( buffer, sizeof( buffer ), myFormat.c_str(),
&t )
== 0 ) {
assert( 0 ) ;
abort() ;
}
myFormat = buffer ;
myState = atStartOfLine ;
}

void endRecord()
{
assert( myState != inactive ) ;
if ( myState != atStartOfLine ) {
myOwner->sputc( 'n' ) ;
}
myOwner->sync() ;
myState = inactive ;
}

private:
enum State
{
inactive,
atStartOfLine,
inLine
} myState ;
std::string myFormat ;
std::string myHeader ;
std::streambuf* myOwner ;
} ;

class LogRecordHandler
: public Gabi::OutputStreamWrapper::LifetimeHandler
{
public:
virtual void constructed( OutputStreamWrapper& what )
{
dynamic_cast< Gabi::FilteringOutputStreambuf* >(
what.stream()->rdbuf())
->inserter().startRecord() ;
}
virtual void destructed( OutputStreamWrapper& what )
{
dynamic_cast< Gabi::FilteringOutputStreambuf* >(
what.stream()->rdbuf())
->inserter().endRecord() ;
}
} ;

(Note qu'écrire un bon wrapper n'est pas forcément trivial,
parce qu'il faut compter avec les copies. Une fois qu'on aura
le support pour la sémantique de move, il sera probablement
préférable de se servir d'une classe dérivée d'ostream. Pour
l'instant, ça ne marche pas, à cause des copies, et le fait
qu'ostream ne supporte pas la copie.)

En ce qui concerne les logs, la plupart du temps, je crée le
ostream pour les logs, avec un
Gabi::FilteringOutputStreambuf<LogInserter>, et des streambuf
finaux déterminé par un fichier de configuration, dès le
démarrage. De même pour le LogRecordHandler. Ensuite, j'ai une
fonction log qui acquiert un lock, que la fonction
LogRecordHandler::destructed libère, de façon à ce que le log
soit thread safe. Aussi, l'appel à la fonction qui renvoie le
flux de log est en général enrobé dans un macro, qui lui passe
automatiquement __FILE__ et __LINE__, qu'elle passe ensuite À
startRecord, qui s'en tient compte dans le formattage.

--
James Kanze (GABI Software) email:
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
Sylvain
James Kanze wrote on 06/03/2007 14:45:



quelques remarques, pour (garder) la forme ;)

class LogInserter : public Gabi::SimpleInserter
{
public:
explicit LogInserter(
std::string const& format = "%Y-%m-%d %H:%M:%S: " )


pourquoi "explicit" ? std::string ne peut pas être construit à partir
d'un char* ? cela a un sens (fort) lorsqu'un seul constructeur est défini ?


void startRecord(){
time_t now = time( NULL ) ;
tm t = *localtime( &now ) ;


pourquoi recopier la struct tm sur la pile ?
le pointeur retourné est suffisant

char buffer[ 1000 ] ;


vraiment gourmand en pile !!

if ( strftime( buffer, sizeof( buffer ), myFormat.c_str(), &t )


surtout pour repasser ce pointeur.

[...]


bon, le reste (donc juste l'essentiel) c'est du tout bon ...

Sylvain.

Avatar
Jean-Marc Desperrier
Sylvain wrote:
James Kanze wrote on 06/03/2007 14:45:
time_t now = time( NULL ) ;
tm t = *localtime( &now ) ;


pourquoi recopier la struct tm sur la pile ?
le pointeur retourné est suffisant


C'est une demi-solution au problème de multi-thread de localtime.

localtime utilise un buffer statique : en le recopiant immédiatement on
réduit le risque qu'il soit écrasé par un autre thread. Sans l'empécher.

J'ai du code qui utilise plutôt que localtime une macro à laquelle on
passe en argument now et tm t :
- la macro appelle localtime_r là où il est disponible,
- sous Windows elle appelle localtime en faisant la même chose
qu'au-dessus. Pour Windows, ce n'est pas grave, car le buffer utilisé
par localtime est en fait du thread specific memory.

Pour un système où on a ni l'un, ni l'autre, il vaut mieux réimplémenter
complètement un équivalent de localtime_r.


Avatar
James Kanze
Sylvain wrote:
James Kanze wrote on 06/03/2007 14:45:

quelques remarques, pour (garder) la forme ;)

class LogInserter : public Gabi::SimpleInserter
{
public:
explicit LogInserter(
std::string const& format = "%Y-%m-%d %H:%M:%S: " )


pourquoi "explicit" ? std::string ne peut pas être construit à partir
d'un char* ? cela a un sens (fort) lorsqu'un seul constructeur est défi ni ?


Afin qu'il n'y a pas de conversion implicite std::string vers
LogInserter. C-à-d que si on a besoin d'un LogInserter, il faut
bien écrire « LogInserter( "format" ) » ; « "format" » tout
court ne marche pas.

En général, j'évite des conversions implicites chaque fois que
je peux. Et en général, j'estîme que l'utilisation d'explicit
dans le cas des constructeurs à un seul paramètre doit être un
automatisme, dont on s'écarte uniquement quand on veut
explicitement une conversion implicite (ce qui doit être assez
rare).

void startRecord(){
time_t now = time( NULL ) ;
tm t = *localtime( &now ) ;


pourquoi recopier la struct tm sur la pile ?


Habitude. Dans la pratique, j'utilise prèsque toujours la
fonction non standard localtime_r, qui exige l'adresse du buffer
sur la pile (et qui marche aussi dans une contexte
multithreaded).

Toujours est-il que je trouve que limiter l'utilisation des
données statiques non-const une bonne chose en général. Ce n'est
pas le cas ici, mais que se passera-t-il avec le pointeur si
ensuite j'appelais une autre fonction qui elle aussi appelait
localtime ? En copiant la valeur, je suis à l'abris des tels
problèmes. (C'est de la programmation défensive.)

Et pourquoi pas la copier ? Je ne vois pas d'aspects négatifs.

Encore, en général : il s'agit ici d'un type à sémantique de
valeur. Et les valeurs, en général, je ne les manipule pas à
travers les pointeurs. Et je ne me laisse pas influencer par le
fait que la norme C s'est trompé dans sa conception. (En fait,
la raison que cette fonction renvoie un pointeur, et non une
struct, est simple : elle existait avant que le C supporte le
renvoie des struct. Le choix est donc justifié.)

le pointeur retourné est suffisant

char buffer[ 1000 ] ;


vraiment gourmand en pile !!


Qui peut le plus, peut le moins:-). Mais je ne veux pas courir
la risque. (Dans un code de production, j'ai une classe
DynamicBuffer que j'utiliserais, quelque chose du genre :

Gabi::DynamicBuffer< char, 100 >
buffer ;
while ( strftime( buffer, buffer.size(), myFormat.c_str(), &t )
== 0 ) {
buffer.setMinimumSize( buffer.size() * 2 ) ;
}

mais aujourd'hui, on pourrait faire la même chose avec
std::vecctor, et bientôt, directement dans le std::string
ciblé :

while ( strftime( myHeader.data(), myHeader.size(),
myFormat.c_str(), &t )
== 0 ) {
myHeader.resize( myHeader.size() * 2 ) ;
}

(Si j'ai bien compris, la proposition en a déjà été acceptée par
le comité pour faire partie de la prochaine version de la
norme.) Ça a l'avantage d'éviter la copie, mais surtout, la
chaîne arrive très vite à sa taille maximum, et ensuite, il n'y
a plus d'allocations dynamiques non plus. (Et j'ai déjà eu le
cas où les allocations dynamiques dans la génération du log
était une cause de rallentissement importante de l'application.
J'aime des logs détaillés ; il faut donc prêter un peu
d'attention à la performance quand même.)

if ( strftime( buffer, sizeof( buffer ), myFormat.c_str(), &t )


surtout pour repasser ce pointeur.

[...]


bon, le reste (donc juste l'essentiel) c'est du tout bon ...


J'espère. Je l'utilise depuis des années, dans le code de
production:-).

--
James Kanze (GABI Software) email:
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
James Kanze
Jean-Marc Desperrier wrote:
Sylvain wrote:
James Kanze wrote on 06/03/2007 14:45:
time_t now = time( NULL ) ;
tm t = *localtime( &now ) ;


pourquoi recopier la struct tm sur la pile ?
le pointeur retourné est suffisant


C'est une demi-solution au problème de multi-thread de localtime.

localtime utilise un buffer statique : en le recopiant immédiatement on
réduit le risque qu'il soit écrasé par un autre thread. Sans l'emp écher.

J'ai du code qui utilise plutôt que localtime une macro à laquelle on
passe en argument now et tm t :
- la macro appelle localtime_r là où il est disponible,
- sous Windows elle appelle localtime en faisant la même chose
qu'au-dessus. Pour Windows, ce n'est pas grave, car le buffer utilisé
par localtime est en fait du thread specific memory.

Pour un système où on a ni l'un, ni l'autre, il vaut mieux réimpl émenter
complètement un équivalent de localtime_r.


localtime_r fait partie de Posix. Windows a localtime_s (qui
fait partie d'un TR de C). C'est à peu près inconcevable qu'un
système qui offre du multithreading n'offre pas quelque chose
d'équivalent.

C'est vrai que j'ai l'habitude de déclarer l'objet local parce
que dans la production, je ne me sers que du localtime_r, qui en
a besoin. Mais dans l'ensemble, je crois que c'est une bonne
habitude. Que se passe-t-il, par exemple, si entre l'appel à
localtime et l'utilisation de l'objet, tu appelle une autre
fonction qui appelle aussi localtime ? Le choix de renvoyer un
pointeur, plutôt qu'une struct, est une erreur. Compréhensible
du point de vue de l'histoire, mais certainement pas ce qu'exige
la bonne conception aujourd'hui. En faisant comme je fais
ci-dessus, je ne fais que faire ce que la fonction devait faire
elle même.

--
James Kanze (GABI Software) email:
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



1 2