【搬運】實現一個有意思的TODO宏

實現一個能產生warning的TODO宏,用於在代碼裏做備忘,效果:


下面一步步來實現這個宏。


Let’s do it

手動讓編譯器報警(報錯)可以用以下幾個方法:

#warning sunnyxx
#error sunnyxx
#pragma message "sunnyxx"
#pragma GCC warning "sunnyxx"
#pragma GCC error "sunnyxx"

但我們知道,帶#的預處理指令是無法被#define的。好在C99提供了一個_Pragma運算符可以把部分#pragma指令字符串化:

#pragma message "sunnyxx"
// 等價於
_Pragma("message \"sunnyxx\"") // 需要注意雙引號的轉義
// 或
_Pragma("message(\"sunnyxx\")") // 需要注意雙引號的轉義

利用這個特性,我們就可以將warning定義成宏

#define SOME_WARNING _Pragma("message(\"報告大王!\")")
int main() {
    SOME_WARNING // [!]報告大王!
    return 1;
}

接下來,我們讓這個宏能夠接受入參,並顯示到warning中去,這裏會面臨宏的基本用法的考驗。

#define STRINGIFY(S) #S
#define PRAGMA_MESSAGE(MSG) _Pragma(STRINGIFY(message(MSG)))

個人認爲不太可能在一個宏定義中完成這件事,需要用到輔助宏:STRINGIFY(S) 將入參轉化成字符串,省去了_Pragma中全串加轉義字符的困擾。
這時,一個基本功能的TODO宏就完成了,下面向其中加入額外的信息

// 兩個已有的宏
#define STRINGIFY(S) #S
#define PRAGMA_MESSAGE(MSG) _Pragma(STRINGIFY(message(MSG)))
// 延遲1次展開的宏
#define DEFER_STRINGIFY(S) STRINGIFY(S)
// 下面的宏在第一行用`\`折行
#define FORMATTED_MESSAGE(MSG) "[TODO-" DEFER_STRINGIFY(__COUNTER__) "] " MSG " \n"  \
    DEFER_STRINGIFY(__FILE__) " line " DEFER_STRINGIFY(__LINE__)

其中涉及到的知識:

  • 兩個常量字符串可以拼接成一個整串 “123””456” => “123456”
  • 使用到3個預定義宏__COUNTER__宏展開次數的計數器,全局唯一;__FILE__當前文件完整目錄字符串;__LINE__在當前文件第幾行
  • 在字符串中預定義宏應延時展開,如果將上面的DEFER_STRINGIFY換成STRINGIFY的話,如__LINE__就不能被正確展開成行數,而是成了一個常量字符串"__LINE__"
  • 爲了美化,warning message中可以使用\n換行

於是,使用FORMATTED_MESSAGE(MSG)宏就可以將帶文件路徑、序號、行數等信息加入到最終的warning中。


其實到這步已經OK了,爲了讓這個宏更加搶眼,還可以借鑑RAC,把宏定義成前面加@的形式:

#define KEYWORDIFY try {} @catch (...) {}

將最終的宏定義前面加上上面的宏後,使用時就可以加@前綴了(空的try-catch會被編譯器優化,所以沒啥性能損耗)


最終版本

#define STRINGIFY(S) #S
#define DEFER_STRINGIFY(S) STRINGIFY(S)
#define PRAGMA_MESSAGE(MSG) _Pragma(STRINGIFY(message(MSG)))
#define FORMATTED_MESSAGE(MSG) "[TODO-" DEFER_STRINGIFY(__COUNTER__) "] " MSG " \n" \
DEFER_STRINGIFY(__FILE__) " line " DEFER_STRINGIFY(__LINE__)
#define KEYWORDIFY try {} @catch (...) {}
// 最終使用下面的宏
#define TODO(MSG) KEYWORDIFY PRAGMA_MESSAGE(FORMATTED_MESSAGE(MSG))

What’s more

除此之外,還研究了半天如何在宏裏面定義一個註釋,這樣就可以偷偷寫// TODO: ...的註釋,讓Xcode導航欄中也出現這個TODO了:

但很可惜沒有找到一個可行的方法,歡迎一起解決。
Xcode插件《XTodo》也是利用這個特性,可以嘗試下。

如果需要一個產生error的宏,將這裏替換成這樣就好了:_Pragma(STRINGIFY(GCC error(MSG)))

同時,上面的代碼在《github上》可以找到。也歡迎關注微博@我就叫Sunny怎麼了一起交流。

References

http://clang.llvm.org/docs/UsersManual.html
https://gcc.gnu.org/onlinedocs/cpp/Pragmas.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章