巨集在 C 語言中非常重要,但在 C++ 中卻無甚大用,普遍的共識:儘量避免使用巨集 C++ 之父 Bjarne 在《C++ Programming Language》中寫到 Avoid macros 《Effective C++》 條款 2 Prefer const, enum, and inline ...
巨集在 C 語言中非常重要,但在 C++ 中卻無甚大用,普遍的共識:儘量避免使用巨集
C++ 之父 Bjarne 在《C++ Programming Language》中寫到
- Avoid macros
《Effective C++》 條款 2
- Prefer const, enum, and inline to #define
谷歌 C++ 編碼規範,關於巨集的描述
- Avoid defining macros, especially in headers
- Do not use macros to define pieces of a C++ API
1 禁用巨集
谷歌 C++ 規範中,禁用巨集的情況有三種:頭文件、API 介面、程式文本
頭文件中禁用巨集,規範里寫的很明確:
- Don't define macros in a
.h
file.
對於 C++ API 介面,則是:
- Do not use macros to define pieces of a C++ API
因此,如下形式的巨集,是禁止的
class PANDA_TYPE(Foo) { // ... public: EXPAND_PUBLIC_PANDA_API(Foo) EXPAND_PANDA_COMPARISONS(Foo, ==, <) };
程式文本中禁用巨集,尤其是用 ## 來替換變數名
- Don't use macros for program text manipulation
- Prefer not using
##
to generate function/class/variable names.
例如,下麵代碼是要避免的
#define CAT(a, b) a ## b #define STRINGIFY(a) #a void f(int x, int y) { string CAT(x, y) = "asdf"; // BAD: hard for tools to handle (and ugly) string sx2 = STRINGIFY(x); // ... }
2 替代巨集
《Effective C++》 條款 2:用 const, enum 或 inline 來替代巨集
用巨集來表示常量和函數,是不推薦的
#define PI 3.14 #define SQUARE(a, b) (a * b)
可用 constexpr 和 模板函數來替代,這樣的好處:constexpr 定義的常量 kPI 會進入符號表,能被編譯器識別到,編譯報錯時會提示 kPI 錯誤
而定義在 .h 中的巨集,如果編譯出錯,只會提示 3.14 這個數值的錯誤,對於不是自己寫的頭文件,且常數含義未知時,很難查到錯誤來源
constexpr double kPI = 3.14; template<typename T>
T square(T a, T b) { return a * b; }
同樣,如下代碼也是需要避免的
// webcolors.h (third party header) #define RED 0xFF0000 #define BLUE 0x0000FF // productinfo.h, the following define product subtypes based on color #define RED 1 #define BLUE 2 int web = BLUE; // web == 2; probably not what was desired
可用 enum class 來代替,在 C++11 之 enum class 中也有提及
enum class Web_color { red = 0xFF0000, green = 0x00FF00, blue = 0x0000FF }; enum class Product_info { red = 0, purple = 1, blue = 2 }; int webby = blue; // error: be specific Web_color web = Web_color::blue;
3 使用巨集
雖然巨集在 C++ 中如此被嫌棄,但為了相容 C 語言,也不能直接將其刪掉,這也是阻礙 C++ 發展的歷史包袱
在某些方面,巨集還是有點價值的,比如:頭文件的保護巨集
#ifndef FOO_BAR_BAZ_H_ #define FOO_BAR_BAZ_H_ ... #endif // FOO_BAR_BAZ_H_
還有一些預定義好的巨集
__cpluplus __DATE__ __FILE__ __LINE__
在代碼可讀性上,巨集往往會有意想不到的效果,如《The Art of Readable Code》中的例子
void AddStats(const Stats& add_from, Stats* add_to) { add_to->set_total_memory(add_from.total_memory() + add_to->total_memory()); add_to->set_free_memory(add_from.free_memory() + add_to->free_memory()); add_to->set_swap_memory(add_from.swap_memory() + add_to->swap_memory()); add_to->set_status_string(add_from.status_string() + add_to->status_string()); add_to->set_num_processes(add_from.num_processes() + add_to->num_processes()); ... }
為了增強可讀性,使用巨集定義,可改為如下形式
void AddStats(const Stats& add_from, Stats* add_to) { #define ADD_FIELD(field) add_to->set_##field(add_from.field() + add_to->field()) ADD_FIELD(total_memory); ADD_FIELD(free_memory); ADD_FIELD(swap_memory); ADD_FIELD(status_string); ADD_FIELD(num_processes); ... #undef ADD_FIELD }
當必須使用巨集時,註意如下幾點:
- If you must use macros, use names with capital letters
- Name macros with a project-specific prefix
#define
macros right before you use them, and#undef
them right after.
參考資料
谷歌 C++ 編碼規範 - Preprocessor Macros
《The Art of Readable Code》 chapter 8
後記
寫完博文,當我還沉浸在搞清一個 C++ 知識點的興奮中時,突然想到魯迅筆下的《孔乙己》,這篇博文,不就是教茴字四種寫法的現代版麽?
孔乙己的悲劇,更多是因時代巨變所致,是舊社會一代讀書人的命運縮影,如果時代沒有變,興許茴字的寫法,也是科舉考試中的一個知識點。
然而,孔乙己還是有一技之長的,"幸而寫得一筆好字,便替人家抄抄書,換一碗飯吃"。在如今經濟停滯甚至衰退的浪潮下,我又有什麼一技之長 "換一碗飯吃" 呢?
寫到此,我也沒有答案,孔乙己 = 恐怕以為是自己,只能以《孔乙己》的結尾警示自己:我到現在終於沒有見——大約孔乙己的確失業了...
原文鏈接: http://www.cnblogs.com/xinxue/
專註於機器視覺、OpenCV、C++ 編程