2013-08-04 16 views
6

Recentemente ho letto Effective C++ Second Edition di Scott Meyers per migliorare le best practice C++. Uno dei suoi elementi elencati incoraggia i programmatori C++ ad evitare le macro pre-processore e "preferisce il compilatore". È arrivato a dire che non ci sono quasi ragioni per le macro in C++ a parte #include e # ifdef/# ifndef.Le macro di pre-processore per le istruzioni di registro di debug hanno una posizione in C++?

sono d'accordo con il suo ragionamento, come si può realizzare la seguente macro

#define min(a,b) ((a) < (b) ? (a) : (b)) 

con il seguente linguaggio C++ dispone

template<class T> 
inline const T & min(const T & a, const T & b) { 
    return a < b ? a : b; 
} 

dove in linea dà il compilatore la possibilità di rimuovere la chiamata di funzione e inserisci codice e modello inline in grado di gestire più tipi di dati con operatore sovraccarico o integrato>.

EDIT-- Questa dichiarazione modello non corrisponde completamente alla macro indicata se il tipo di dati di a e b differiscono. Vedi il commento di Pete per un esempio.

Tuttavia, sono curioso di sapere se l'utilizzo di macro per la registrazione di debug è un uso valido in C++. Se il metodo che presento di seguito non è una buona pratica, qualcuno sarebbe gentile a suggerire un modo alternativo?

Sono stato codificato in Objective-C per l'ultimo anno e uno dei miei motori 2D preferiti (cocos2d) ha utilizzato una macro per creare istruzioni di registrazione. La macro è la seguente:

/* 


* if COCOS2D_DEBUG is not defined, or if it is 0 then 
* all CCLOGXXX macros will be disabled 
* 
* if COCOS2D_DEBUG==1 then: 
*  CCLOG() will be enabled 
*  CCLOGERROR() will be enabled 
*  CCLOGINFO() will be disabled 
* 
* if COCOS2D_DEBUG==2 or higher then: 
*  CCLOG() will be enabled 
*  CCLOGERROR() will be enabled 
*  CCLOGINFO() will be enabled 
*/ 


#define __CCLOGWITHFUNCTION(s, ...) \ 
NSLog(@"%s : %@",__FUNCTION__,[NSString stringWithFormat:(s), ##__VA_ARGS__]) 

#define __CCLOG(s, ...) \ 
NSLog(@"%@",[NSString stringWithFormat:(s), ##__VA_ARGS__]) 


#if !defined(COCOS2D_DEBUG) || COCOS2D_DEBUG == 0 
#define CCLOG(...) do {} while (0) 
#define CCLOGWARN(...) do {} while (0) 
#define CCLOGINFO(...) do {} while (0) 

#elif COCOS2D_DEBUG == 1 
#define CCLOG(...) __CCLOG(__VA_ARGS__) 
#define CCLOGWARN(...) __CCLOGWITHFUNCTION(__VA_ARGS__) 
#define CCLOGINFO(...) do {} while (0) 

#elif COCOS2D_DEBUG > 1 
#define CCLOG(...) __CCLOG(__VA_ARGS__) 
#define CCLOGWARN(...) __CCLOGWITHFUNCTION(__VA_ARGS__) 
#define CCLOGINFO(...) __CCLOG(__VA_ARGS__) 
#endif // COCOS2D_DEBUG 

Questa macro fornisce un'incredibile utilità che desidero incorporare nei miei programmi C++. Scrivere una dichiarazione di registro utile è così semplice come

CCLOG(@"Error in x due to y"); 

Che cosa è ancora migliore, è che se il COCOS2D_DEBUG è impostato su 0, allora queste dichiarazioni non vedono mai la luce del giorno. Non esiste un sovraccarico per il controllo di un'istruzione condizionale per verificare se è necessario utilizzare le istruzioni di registrazione. Questo è conveniente quando si passa dalla fase di sviluppo alla produzione. Come si potrebbe ricreare lo stesso effetto in C++?

Quindi questo tipo di macro appartiene a un programma C++? C'è un modo migliore, più C++ per farlo?

+4

Credo che il tuo "CCLOG()" sia un eccellente esempio di dove le macro sono utili e appropriate. IMHO ... – paulsm4

+1

Um, quella funzione di template 'min' fa ** not ** fa la stessa cosa della macro. Provalo con 'min (1, 2L)'. –

+0

@PeteBecker Non sono un guru con i modelli, ma potrebbe essere il caso perché 1 è un int e 2L è un lungo? La dichiarazione modello che ho fornito genererà solo funzioni per due oggetti dello stesso tipo. –

risposta

6

In primo luogo, la dichiarazione di Scott è stata fatta in un momento in cui le macro erano notevolmente sovrautilizzate, per ragioni storiche. Mentre è in genere vero, ci sono alcuni casi in cui i macro hanno senso. Di questi è la registrazione, perché solo una macro può automaticamente inserire __FILE__ e __LINE__. Inoltre, solo una macro può risolvere in assoluto (anche se, in base alla mia esperienza, , questo non è un grosso problema).

I macro come quelli visualizzati non sono molto frequenti in C++.Ci sono due solite varianti per la registrazione:

#define LOG(message) ... << message ... 

che permette messaggi in forma " x = " << x, e può essere completamente soppressa ridefinendo la macro, e

#define LOG() logFile(__FILE__, __LINE__) 

dove logFile restituisce un wrapper per un std::ostream, che definisce operator<<, e permette che cose come:

LOG() << "x = " << x; 

Eseguito in questo modo, tutte le espressioni a destra di LOG() verranno sempre valutate, ma eseguite correttamente, nessuna formattazione verrà eseguita a meno che il registro non sia attivo.

2

Ci sono cose "giuste" per usare le macro e ci sono cattivi usi delle macro. Utilizzare le macro in cui le funzioni funzionano è una cattiva idea. L'utilizzo di macro in cui le funzioni NON fanno la stessa cosa è perfettamente buono nel mio libro.

ho abbastanza spesso usano costrutti come questo:

#defien my_assert(x) do { if (!x) assert_failed(x, #x, __FILE__, __LINE__); } while(0) 

template<typename T> 
void assert_failed(T x, const char *x_str, const char *file, int line) 
{ 
    std::cerr << "Assertion failed: " << x_str << "(" << x << ") at " << file << ":" << line << std::endl; 
    std::terminate(); 
} 

Un altro trucco utilizzando il "gestore" stringizing è qualcosa di simile:

enum E 
{ 
    a, 
    b, 
    c, 
    d 
}; 

struct enum_string 
{ 
    E v; 
    const char *str; 
}; 

#define TO_STR(x) { x, #x } 

enum_string enum_to_str[] = 
{ 
    TO_STR(a), 
    TO_STR(b), 
    TO_STR(c), 
    TO_STR(d), 
    }; 

Salva una bel po 'di ripetere cose ...

Quindi, sì, è utile in alcuni casi.