2009-10-08 8 views
6

Sto creando un registratore con le seguenti sezioni:stringstream problema ritorno ostream temporanea

// #define LOG(x) // for release mode 
#define LOG(x) log(x) 

log(const string& str); 
log(const ostream& str); 

Con l'idea di fare:

LOG("Test"); 
LOG(string("Testing") + " 123"); 
stringstream s; 
LOG(s << "Testing" << 1 << "two" << 3); 

Tutto questo funziona come previsto, ma quando lo faccio:

LOG(stringstream() << "Testing" << 1 << "two" << 3); 

non funziona:

void log(const ostream& os) 
{ 
    std::streambuf* buf = os.rdbuf(); 
    if(buf && typeid(*buf) == typeid(std::stringbuf)) 
    { 
    const std::string& format = dynamic_cast<std::stringbuf&>(*buf).str(); 
    cout << format << endl; 
    } 
} 

risultati in "formato" contenente dati indesiderati anziché la solita stringa corretta.

Penso che ciò sia dovuto al fatto che l'ostream temporaneo restituito dall'operatore < < sopravvive allo stringstream da cui proviene.

Oppure ho sbagliato?

(Perché la stringa() opera in questo modo? E 'perché restituisce un riferimento a se stesso? Sto assumendo di sì.)

Mi piacerebbe davvero farlo in questo modo, come vorrei essere eliminando il un'allocazione aggiuntiva quando si accede alla modalità di rilascio.

Qualsiasi suggerimento o trucchetto per farlo in questo modo sarebbe ben accetto. Nella mia attuale soluzione ho molte funzioni di registro diverse e sono tutte più complesse di così. Quindi preferirei che fosse implementato in qualche modo nel codice chiamante. (E non modificando il mio # define se possibile)

Tanto per dare un'idea, un esempio di uno dei miei # definisce effettivi:

#define LOG_DEBUG_MSG(format, ...) \ 
    LogMessage(DEBUG_TYPE, const char* filepos, sizeof(__QUOTE__(@__VA_ARGS__)), \ 
    format, __VA_ARGS__) 

che corrisponde varargs funzioni di log-printf come prendere char *, string() e ostream() così come le funzioni non vararg assumono string(), exception() e HRESULT.

+0

Cosa intendi per "non funziona"? –

+0

Hai ragione, dovresti prendere una copia della stringa, quindi 'format' dovrebbe essere di tipo' std :: string' non di tipo 'const std :: string &'. Puoi comunque mettere il 'dynamic_cast' nell'espressione' cout' e perdere la variabile del tutto. – KayEss

+0

No, non ho bisogno di fare una copia. Il contenuto della stringa restituita da str() è garantito per rimanere costante (nello stesso modo in cui string :: c_str()) tra le chiamate successive a questi metodi. Per quanto riguarda il motivo per cui lo sto facendo, ho bisogno della stringa di formato poiché in realtà voglio passare a una funzione a parametro singolo prendendo una stringa, oa un metodo VARARGS che prende un carattere * a seconda di quali altri parametri sono stati ricevuti. (Ma tutto questo è al di fuori della portata della mia domanda - a cui è stata data una risposta soddisfacente) – Marius

risposta

7

I think Vedo cosa sta succedendo.Questo produce i risultati attesi:

log(std::stringstream() << 1 << "hello"); 

mentre questo non:

log(std::stringstream() << "hello" << 1); 

(scrive un numero esadecimale, seguita dalla cifra "1")

Alcuni elementi per la spiegazione :

  • un rvalue non può essere associato a un riferimento non const
  • E 'OK per richiamare le funzioni di membro a titolo temporaneo
  • std :: ostream ha un operatore di membro < < (void *)
  • std :: ostream ha un operatore di membro < < (int)
  • Per char * l'operatore non è membro, è operatore di < < (std :: ostream &, const char *)

Nel codice precedente, std :: stringstream() crea un temporaneo (un rvalue). La sua durata non è problematica, in quanto deve durare per l'intera espressione completa in cui è dichiarata (vale a dire, finché non viene restituita la chiamata a log()).

Nel primo esempio, tutto funziona bene perché l'operatore membro < < (int) viene dapprima chiamato, e quindi il riferimento restituito può essere passato all'operatore < < (ostream &, const char *)

In il secondo esempio, operatore < < (non può essere chiamato con "std :: stringstream()" come primo argomento, poiché ciò richiederebbe che fosse associato a un riferimento non-const. Tuttavia, l'operatore membro < < (void *) è ok, dato che è un membro

A proposito: perché non definire la funzione log() come:

void log(const std::ostream& os) 
{ 
    std::cout << os.rdbuf() << std::endl; 
} 
+1

Grazie per le informazioni! Questo mi ha portato alla mia soluzione preferita ... Sapevo di essere sulla strada giusta. Il modo migliore per farlo funzionare coerentemente è quindi il seguente: 'log (stringstream(). Flush() <<" ciao "<< 1);' – Marius

+0

PS: cosa significa BYT? E come definisci la funzione 'log()' in modo diverso da me? – Marius

+0

Oups! È un errore di battitura. Volevo scrivere "By The Way". Corretto –

6

alterare il vostro LOG() macro a questo:

#define LOG(x) do { std::stringstream s; s << x; log(s.str()); } while(0) 

che vi permetterà di utilizzare la seguente sintassi nei log di debug, quindi non c'è bisogno di costruire manualmente il flusso di stringa.

Quindi definirlo a zero per il rilascio e non avrai allocazioni aggiuntive.

+0

Non è necessario il do/mentre intorno al blocco. Puoi avere parentesi graffe libere solo per controllare la portata delle espressioni. – KayEss

+0

Il mio obiettivo principale è l'ottimizzazione. Per quanto mi piacerebbe mantenere una singola macro LOG() che funziona per molti tipi diversi passati, probabilmente dovrò creare un'eccezione per distinguerla. il problema è che in effetti resteranno alcuni registri nella modalità di rilascio (a seconda della gravità della registrazione) e per questi non vorrei l'overhead descritto sopra, quindi potrei aver bisogno di creare l'eccezione #define per ogni gravità. Ho cercato di mantenere il formato della sintassi di registrazione invariato il più possibile su questo progetto legacy. – Marius

+0

Lo segnalo come utile, ma non lo contrassegnerò come soluzione ancora. – Marius