Newsgroup: it.comp.lang.c++
Titolo: Operatori consecutivi

Sto realizzando una classe per generare i log all'interno di un programma, e
ho voluto utilizzare lo stesso sistema degli ostream: l'operatore <<. La
classe accetta vari costruttori con stream di vari tipi (su console e su
file ad esempio) e mantiene un riferimento allo stream da usare, poi con una
template function "trasmette" l'operatore << allo stream interno. Ecco un
riassunto:
class logger
{
private:
    ostream *dest;
public:
    logger()
    {
        dest = &cout;    // log standard su cout
    };
    logger(ostream &ost)
    {
        dest = &ost;    // log su uno stream specificato
    };
    template <class T> logger & operator << (T data)
    {
        *dest << data;
    };
};
Inoltre la classe deve formattare automaticamente ogni messaggio in un modo
simile a questo:
TIPO : HH:MM:SS GG:MM:AAAA : TESTO
Ad esempio:
ERRORE : 22:55:36 10/11/2000 : E' successo un casino!
CHECK : 21:13:40 11/11/2000: tutto ok
Per realizzare ciò, ho creato un tipo enumerato per specificare i tipi di
log, e ho specializzato l'operatore << per questo tipo:
enum log_type
{
    LOG_ERROR,
    LOG_CHECK,
    LOG_NETWORK,
    LOG_MEMORY,
    LOG_DEBUG,
    // ...
};
logger & logger::operator << (log_type l)
{
    switch(l)
    {
    case LOG_ERROR:
        *dest << "ERRORE : ";
        break;
    case LOG_CHECK:
        *dest << "CHECK : ";
        break;
    case LOG_NETWORK:
        *dest << "RETE : ";
        break;
    // ...
    };
    *dest << ora << " " << data << " : ";    // ovviamente ora e data sono
da calcolare
    return (*this);
};
In questo modo, una chiamata del tipo
logger << LOG_ERROR << "E' successo un casino!" << endl;
produrra' il risultato voluto.
Non sono però molto soddisfatto, perché questa classe, così definita,
richiede un utilizzo estremamente corretto, e per la precisione occorre
passare prima un tipo di log, poi un testo, infine un endl per andare a
capo. Vorrei evitare questa macchinosità, possibile fonte di errori; si
pensi ad esempio a cosa accadrebbe dimenticandosi di passare un tipo di log
come primo parametro. La soluzione ideale sarebbe che la classe potesse
capire automaticamente quando è arrivata alla fine di una sequenza di
operatori. Mi spiego meglio: se difronte a una chiamata come
logger << LOG_ERROR << "Parametro non corretto: " << npar << " nella
funzione " << funcname;
la classe potesse capire che alla fine della riga è stato prodotto un
messaggio completo, potrebbe automaticamente inserire un ritorno a capo e
alla successiva chiamata generare l'intestazione appropriata, e magari
inserire un tipo di log di default in mancanza del primo parametro.
Ma, com'è noto, le sequenze di chiamata del tipo
stream << int << char << "testo" << float << endl;
sono rese possibili dal fatto che ogni operatore ritorna un reference alla
classe stessa, e quindi vengono eseguite "a catena", perciò anche se quella
sopra sembra una sola istruzione, in realtà è equivalente a
stream << int;
stream << char;
stream << "testo";
stream << float;
stream << endl;
E quindi la classe non ha modo di capire quando è giunta alla fine di un
messaggio.
Una soluzione potrebbe essere modificare l'operatore << per, ad esempio,
"azzerare" lo stato della classe (quindi inserire un invio e generare un
nuovo header alla successiva chiamata) quando incontra il manipolatore endl,
ma poi occorrerebbe ogni volta ricordarsi di inserirlo. Oppure potrebbe fare
la stessa cosa sul parametro di tipo tipo_log, ma anche questo non mi pare
molto elegante.
Vi vengono in mente soluzioni migliori ?
Massimo
P.S.
Quello che vorrei si realizza molto facilmente con una funzione
simil-printf() a parametri variabili (...), ma mi sembra poco carino in C++,
e poi l'interfaccia degli ostream mi piace di più :-)

 template function "trasmette" l'operatore << allo stream interno.
Mai nulla di nuovo sotto il sole...  si e' gia' parlato in passato di
un operator << che scrive contemporaneamente sullo stream e su un log
file; le soluzioni a suo tempo proposte sono in
http://wwwcdf.pd.infn.it/MLO/logout.tar.gz
 .  Quella che preferisco e'
la seconda.
-- 
Maurizio Loreti                         
http://www.pd.infn.it/~loreti/mlo.html
Univ. of Padova, Dept. of Physics - Padova, Italy            
lor...@pd.infn.it
Murphy's law: "Anything that can go wr
/bin/fortune: segmentation fault - core dumped

 la seconda.
Sě, ok, mi aspettavo una risposta del genere, ma se leggi bene il mio
messaggio, la domanda non era "come realizzare una classe per il log" ma
alcune considerazioni sulle catene di operatori << per un oggetto
simil-ostream...
Massimo

 in C++,
Tieni conto che con una funzione simil-printf hai le stesse
possibilità di sbagliare, anzi la situazione è peggiore.
Manlio Perillo

e poi l'interfaccia degli ostream mi piace di più :-)
Io sarei per una soluzione "simil-printf" per una serie di ragioni...
prestazioni, leggibilita', controllabilita' (es. il primo parametro
potrebbe essere obbligatoriamente la severity del messaggio) e non
ultima quella della problematicita' della sincronizzazione di un
log con interfaccia ostream in una applicazione multithread (argomento
di una discussione abbastanza recente in questo newsgroup).
Il C++ ha un sacco di cose che sono migliori del C in molti
contesti... ma non esiste uno strumento che sia il migliore in
*tutti* i contesti.
Certo il compilatore non si accorge di eventuali incompatiblita' fra
formato e parametri e questo richiede qualche attenzione; ma a mio
parere i vantaggi (nel caso di logging) sono ampiamente superiori
a questo costo.
HTH
Andrea
--
Andrea Griffini, Programmer
http://www.netservers.com/~agriff


Questo è vero, ma è anche vero che fare una cosa del genere
significa crearsi dei problemi da soli
(come se già non ci fossero problemi in condizioni 'normali').
Inoltre se hai:
int f() { /* ... */ return 1; }
int g() { /* ... */ return 2; }
int h() { /* ... */ return 3; }
sullo stream "apparirà"
123.
Le funzioni possono essere valutate nell'ordine che più aggrada
al compilatore, ma a noi non ci interessa più di tanto
(a meno che tali funzioni non facciano qualcosa di strano).
Comunque mi è venuto un dubbio...
Non è che nel mio esempio l'ordine di chiamata di
beginmsg_helper e endmsg_helper sia undefined?
Ma se fosse così significa che i manipolatori diventano
inutilizzabili:
double x = 234.12345;
cout << setprecision(3) << x << '\t' << setprecision(8) << x << '\n';
Forse le cose funzionano perchè in realtà i manipolatori (ad un
parametro) sono funzioni a due argomenti, ma il primo è fornito
in modo implicito.
E per fornire il primo argomento (ostream&), la sequenza
delle operazioni deve essere valutata nel suo ordine
(mi sono spiegato male, ma spero sia chiaro il concetto).
Manlio Perillo

 
Direi di no. Quello che è undefined (in base a quanto spiegato da Andrea)  
è l'ordine di chiamata di beginmsg() e endmsg() e di conseguenza l'ordine
in cui vengono creati i due oggetti logmanip temporanei. Le funzioni
*msg_helper sono invocate in operator<<, quindi non dovrebbero esserci
problemi.
Max

 non ci sono garanzie su quale e' la prima funzione chiamata.
Questa non la sapevo... maggiori dettagli ?
Massimo

 usare i manipolatori.
Non ho mai capito bene come funzionano però, specialmente quelli con
parametri... vedrò di darci un'occhiata.
Massimo
P.S.
Però questo non risolve il problema di alleggerire l'interfaccia evitando di
dover specificare inizio e fine, a me quello interessava :-(

(mi sono spiegato male, ma spero sia chiaro il concetto).
No. Se scrivi
  cout << setprecision(3) << x << ";"
       << setprecision(6) << x << endl;
L'ordine in cui vengono chiamate le funzioni setprecision
non e' prestabilito ma la cosa non comporta problemi
perche' queste funzioni si limitano a restituire due
oggetti in cui sono memorizzati i valori 3 e 6.
Al momento dell'output questi oggetti impostano la precisione.



Era quello che volevo dire (più o meno).
setprecision chiama a sua volta una funzione che 'fa'
il lavoro. Questa funzione richiede un ostream& su cui
fa le modifiche.
Manlio Perillo

 fa le modifiche.
No. setprecision non chiama nessuna funzione ma restituisce
un oggetto. Grazie a questo non ci sono problemi di
sequenzialita' con operator<<.
Secondo alcuni testi di programmazione object oriented
la scelta di "setprecision" come nome sarebbe forse
discutibile visto che quel nome rappresenta una azione.
Setprecision non fa nulla in se... al limite sarebbe
stato meglio "precision_modifier" a chiarire che si
creava un oggetto e basta.
In un programma C++ e' ad esempio permesso scrivere
  setprecision(3);
senza che nessuno stream venga coinvolto.
Queste cose le sto ovviamente dicendo per me e
per vedere se ho capito correttamente.
Non si tratta certamente di una spiegazione
per te visto che sicuramente sono cose ovvie
come lo sono i template e le exceptions ...
(confesso che il tuo commento mi ha intristito un poco...)
HTH(me)
Andrea
--
----------------------------------------------------------------
 <\___/>  Andrea        email: agriff at tin dot it
 / . . \  "6502"          irc: _6502_@EFNET#italia
(  (")  ) Griffini   homepage: 
http://www.netservers.com/~agriff
Sent via Deja.com 
http://www.deja.com/
Before you buy.

 sequenzialita' con operator<<.
Anche qui mi sono spiegato male.
Mi rifacevo all'esempio dello Stroustrup:
struct smanip
{
    ios_base& (*f)(ios_base&, int); // function to be called
    int i;
    smanip(ios_base& (*ff)(ios_base&, int), int ii)
    : f(ff), i(ii) {  }
};
ostream& operator<<(ostream& os, const smanip& m)
{
    return m.f(os, m.i);
}
// funzione helper che 'fa' il lavoro.
// E' a questa a cui mi riferivo
ios_base& set_precision(ios_base& s, int n)
{
    s.setprecision(n);
}
inline smanip setprecision(int n)
{
    return smanip(set_precision, n);
}
Come si vede, la funzione a cui mi riferivo era set_precision.
E' questa funzione che viene chiamata nell'ordine voluto.
P.S.
Certo che č proprio brutto essere fraintesi solo per
aver voluto risparmiare sulle parole da scrivere....
(la comunicazione umana č proprio complicata, altro che C++)
Manlio Perillo

(la comunicazione umana č proprio complicata, altro che C++)
Ahhhh... meno male :) ... per un attimo ho temuto che
cominciassi a scrivere tutto in maiuscolo e a firmare
i messaggi come "il mago del computer" ;)
