2016-05-20 29 views
11

L'applicazione dispone di un sistema di registrazione che consente di abilitare o disabilitare le funzionalità di registrazione dei suoi moduli in fase di runtime. I comandi di registro accettano flussi in ingresso (essendo un'alternativa sicura a "sprintf", non c'è quasi nessuna situazione più fastidiosa di quella se il tuo sistema di debug è la causa di crashCortocircuito di un flusso

Il problema è che se eseguo cose come:

logger.Trace << "Requests pending:" << buffer.findRequests(); 

e findRequests() è di elevata complessità computazionale, anche se la disattivazione livello di log di traccia per il modulo, la ricerca verrà effettuata (durante il montaggio del flusso) prima che venga respinto all'interno del metodo Trace operator<<.

l'ovvio alternativa sarebbe quella di sparpagliare il codice con:

if(logger.Trace.Enabled()) logger.Trace << ... 

Non è bello e non è comodo. Poterla sostituire con una macro utilizzando if, o uno che utilizza && corto circuito, essendo leggermente migliore (può essere utilizzato come rvalue, che segue ritorna filosofia stream bool false disabili stream):

#define TRACE if(logger.Trace.Enabled()) logger.Trace 

    #define TRACE dummyLogVar = logger.Trace.Enabled() && logger.Trace 

Né è particolarmente carino o sicuro. Un collega ha suggerito chiusure:

logger.Trace([&](f){f << "Requests pending:" << buffer.findRequests();}); 

Il .Trace valuterebbe la chiusura solo se tale livello è abilitato. Logicamente, è bello, ma sintatticamente assolutamente orribile. Digitando quel pasticcio: logger.Trace([&](f){f << ... ;}); centinaia di volte?

Esiste un modo più accurato, sicuro e confortevole per impedire la valutazione di un flusso?

+0

Penso che le chiusure siano l'unico modo. Ho dovuto risolvere questo stesso problema prima dell'introduzione delle chiusure, e mi sono ritrovato a ricorrere ai macro e alle chiamate di funzione in stile C. –

+2

_ "Logicamente, è bello, ma sintatticamente assolutamente orribile." _ Hehe benvenuto in C++ –

+0

@LightnessRacesinOrbit: sii felice di non aver usato '<:&:>' per la chiusura. Inciampetto su quello ieri, mi ha lasciato perplesso per un bel po '. In realtà '<:' è un digrafo per '['. Chi al giorno d'oggi usa ancora digrammi e perché? E perché nel mondo C++ 11 ha esteso il set di digraphs e trigraphs ?! Qualcuno ha frainteso la "programmazione integrata" e utilizza il telefono Android con tastiera touchscreen per scrivere applicazioni per PC? –

risposta

8

Le macro possono essere effettivamente utilizzate, ma sono necessarie macro simili a funzioni che accettano l'output come argomento e occorre accertarsi che si tratti di una singola istruzione.

La seconda parte, assicurandosi che il corpo della macro sia una singola istruzione, è semplice e viene solitamente eseguita utilizzando do { ... } while (false).

Anche l'utilizzo di un argomento per la macro non è difficile, a patto che non ci siano virgole nell'argomento. La limitazione della virgola sta includendo l'uso di chiamate di funzione nell'argomento macro con i propri argomenti, il preprocessore è piuttosto stupido qualsiasi utilizza qualsiasi virgola nell'argomento macro come separatore di argomenti per la macro.

Nella sua forma più semplice, senza preoccuparsi con la virgola-limitazione, la macro potrebbe allora essere simile

#define TRACE(output)     \ 
    do         \ 
    {         \ 
     if (logger.Trace.Enabled())  \ 
     {        \ 
      logger.Trace << output;  \ 
     }        \ 
    } while (false) 

Si noti che non v'è alcuna semicilon dopo il while (false).

È quindi utilizzarlo come

TRACE("Requests pending:" << buffer.findRequests()); 

La do { ... } while (false) parte sarà molto probabilmente essere ottimizzato via dal compilatore, lasciando con un semplice controllo if. Se logger.Trace.Enabled() restituisce false, non dovrebbe accadere nulla oltre a tale controllo.

Se si dispone di un compilatore in grado di C++ 11 o versioni successive, è necessario il supporto per variadic macros che dovrebbe aiutare a superare la limitazione relativa alle virgole nell'argomento della macro.

Utilizzo di macro variadic, la macro invece sarebbe simile a questa:

#define TRACE(...)      \ 
    do         \ 
    {         \ 
     if (logger.Trace.Enabled())  \ 
     {        \ 
      logger.Trace << __VA_ARGS__; \ 
     }        \ 
    } while (false) 
+0

È possibile risolvere questo problema senza macro variadic. Definisci 'TRACE' come una macro che non accetta argomenti, dove' TRACE' viene sostituito con una chiamata a una funzione che restituisce un puntatore a una funzione che accetta argomenti variabili. Avere quella chiamata di funzione restituisce una delle due funzioni a seconda della registrazione abilitata. Se la registrazione è abilitata, restituire una funzione che registra effettivamente i dati. Se la registrazione non è abilitata, restituisci una funzione che restituisce semplicemente senza fare nulla: 'void (* traceFunc) (const char * fmt, ...);' quindi '#define TRACE traceFunc()'. Non fa C++ IO, anche se –

+2

@AndrewHenle, ma poi, anche se la registrazione è disabilitata, il messaggio di log è ancora calcolato e quindi scartato, che OP vuole evitare. –

+0

@ el.pescado Vero. Non penso sia possibile risolvere completamente questo aspetto del problema senza utilizzare macro variadiche. Sarebbe certamente interessante vedere una soluzione del genere. Sarebbe interessante anche una soluzione di puro C++ IO - con ciò intendo una che non richiede alcuna parentesi in stile C - semplicemente usa l'operatore "<<". –

0

ho lavorato su questo problema. Finnaly, ho creato una classe Log con questa interfaccia e le macro associate:

class Log 
{ 
    public: 
    static bool trace_is_active(); 
    static bool debug_is_active(); 
    static bool info_is_active(); 
    static bool warning_is_active(); 
    static bool error_is_active(); 
    static void write_as_trace(const std::string& msg); 
    static void write_as_debug(const std::string& msg); 
    static void write_as_info(const std::string& msg); 
    static void write_as_warning(const std::string& msg); 
    static void write_as_error(const std::string& msg); 
}; 

#define LOG_TRACE(X) {if(Log::trace_is_active()){std::ostringstream o__;o__<<X;Log::write_as_trace(o__.str());}} 
#define LOG_DEBUG(X) {if(Log::debug_is_active()){std::ostringstream o__;o__<<X;Log::write_as_debug(o__.str());}} 
#define LOG_INFO(X) {if(Log::info_is_active()){std::ostringstream o__;o__<<X;Log::write_as_info(o__.str());}} 
#define LOG_WARNING(X) {if(Log::warning_is_active()){std::ostringstream o__;o__<<X;Log::write_as_warning(o__.str());}} 
#define LOG_ERROR(X) {if(Log::error_is_active()){std::ostringstream o__;o__<<X;Log::write_as_error(o__.str());}} 

Poi utilizzo è molto semplice e chiaro:

//... 
LOG_WARNING("The variable x = " << x << " is out of range"); 
//... 
LOG_DEBUG("The inverse of the matrix is inv(m) = " << std::endl << inv(m) << std::endl); 

EDIT: Prendo atto che la soluzione con do{...}while(false) è meglio perché la mia soluzione seguita da ; è di due istruzioni e non può essere utilizzata in loop o condizione senza scriverla tra { e }. Ora posso migliorare il mio codice. :-)