2013-08-04 13 views
6

最近,我一直在閱讀Scott Meyers的Effective C++第二版,以改進C++最佳實踐。他列出的項目之一鼓勵C++程序員避免預處理器宏和'喜歡編譯器'。他甚至說除了#include和#ifdef /#ifndef之外,在C++中幾乎沒有宏的原因。調試日誌語句的預處理器宏是否在C++中佔有一席之地?

我同意他的推理,你可以完成下面的宏

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

與下面的C++語言功能

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

,其中內聯給出了編譯器刪除函數調用的選項,插入內聯代碼和可以處理多個數據類型的模板,這些數據類型具有超載或內置>運算符。

編輯 -如果a和b的數據類型不同,此模板聲明將不會完全匹配所述的宏。請參閱皮特的評論爲例。

不過,我很想知道,如果使用宏是爲了調試日誌記錄在C++中的有效使用。如果我在下面介紹的方法不是很好的做法,有人會建議一種替代方法嗎?

我在Objective-C中被編碼的最後一年,我最喜歡的2D引擎(cocos2d的)中的一個使用的宏創建日誌記錄語句。這個宏如下:

/* 


* 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 

這個宏提供了令人難以置信的效用,我將要合併到我的C++程序中。編寫有用的日誌語句很簡單,只要

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

什麼是更好的是,如果COCOS2D_DEBUG設置爲0,那麼這些語句永遠看不到光明的一天。檢查條件語句以查看是否應使用日誌語句沒有開銷。從開發過渡到生產時這很方便。如何在C++中重新創建相同的效果?

那麼,這類型的宏在C++程序屬於?有沒有更好的,更多的C++方式來做到這一點?

+4

我相信你的「CCLOG()」是一個很好的例子,宏是有用和適當的。恕我直言... – paulsm4

+1

嗯,'min'模板函數不會執行與宏相同的操作。試用'min(1,2L)'。 –

+0

@PeteBecker我不是模板的大師,但是可以這樣做,因爲1是一個int和2L是長?我提供的模板聲明只會爲兩個相同類型的對象生成函數。 –

回答

6

首先,斯科特的說法是在同一時間做時宏爲 大大濫用,由於歷史的原因。雖然它通常是 ,但有些情況下宏有意義。 其中日誌記錄,因爲只有一個宏可以自動插入 插入__FILE____LINE__。此外,只有一個宏可以 決心絕對沒有(儘管根據我的經驗,這 是不是一個大問題)。

宏如您展示是不是在C非常頻繁++。有 兩種常用的變體用於記錄:

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

,其允許在形式" x = " << x消息,並且可以是 通過重新定義宏完全抑制,並且

#define LOG() logFile(__FILE__, __LINE__) 

其中logFile返回包裝用於std::ostream,其中 定義爲operator<<,並且允許諸如:

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

完成這種方式後,LOG() 右側的所有表達式將始終進行評估,但正確完成後,除非日誌處於活動狀態,否則不會執行格式設置 。

2

有「正確」的東西來使用宏,並有壞的宏使用。在函數工作的地方使用宏是一個壞主意。在我的書中,使用函數不執行相同操作的宏是非常好的。

我經常使用的結構是這樣的:

#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(); 
} 

使用字符串化「經營者」另一個竅門是這樣的:

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), 
    }; 

節省相當多的重複的東西...

所以,是的,它在某些情況下很有用。