我們來見識一下宏是如何幫助我們自動產生代碼的。如我所說,宏是在符號層次產生代碼。我在分析Boost.Function 模塊時,因爲它使用了大量的宏(宏嵌套,再嵌套),導致我壓根沒看明白代碼。後來發現了一個小型的模板庫ttl,說的 是開發一些小型組件去取代部分Boost(這是一個好理由,因爲Boost確實太大)。同樣,這個庫也包含了一個function庫。 這裏的function也就是我之前提到的functor。ttl.function庫裏爲了自動產生很多類似的代碼,使用了一個宏:
#define TTL_FUNC_BUILD_FUNCTOR_CALLER(n) / template< typename R, TTL_TPARAMS(n) > / struct functor_caller_base##n / ///... 該宏的最終目的是:通過類似於TTL_FUNC_BUILD_FUNCTOR_CALLER(1)的調用方式,自動產生很多functor_caller_base模板: template struct functor_caller_base1 template struct functor_caller_base2 template struct functor_caller_base3 ///... 那麼,核心部分在於TTL_TPARAMS(n)這個宏,可以看出這個宏最終產生的是: typename T1 typename T1, typename T2 typename T1, typename T2, typename T3 ///... 我們不妨分析TTL_TPARAMS(n)的整個過程。分析宏主要把握我以上提到的一些要點即可。以下過程我建議你翻着ttl的代碼, 相關代碼文件:function.hpp, macro_params.hpp, macro_repeat.hpp, macro_misc.hpp, macro_counter.hpp。
so, here we go
分析過程,逐層分析,逐層展開,例如TTL_TPARAMS(1):
#define TTL_TPARAMS(n) TTL_TPARAMSX(n,T) => TTL_TPARAMSX( 1, T ) #define TTL_TPARAMSX(n,t) TTL_REPEAT(n, TTL_TPARAM, TTL_TPARAM_END, t) => TTL_REPEAT( 1, TTL_TPARAM, TTL_TPARAM_END, T ) #define TTL_TPARAM(n,t) typename t##n, #define TTL_TPARAM_END(n,t) typename t##n #define TTL_REPEAT(n, m, l, p) TTL_APPEND(TTL_REPEAT_, TTL_DEC(n))(m,l,p) TTL_APPEND(TTL_LAST_REPEAT_,n)(l,p) 注意,TTL_TPARAM, TTL_TPARAM_END雖然也是兩個宏,他們被作爲TTL_REPEAT宏的參數,按照prescan規則,似乎應該先將 這兩個宏展開再傳給TTL_REPEAT。但是,如同我在前面重點提到的,這兩個宏是function-like macro,使用時需要加括號, 如果沒加括號,則不當作宏處理。因此,展開TTL_REPEAT時,應該爲: => TTL_APPEND( TTL_REPEAT_, TTL_DEC(1))(TTL_TPARAM,TTL_TPARAM_END,T) TTL_APPEND( TTL_LAST_REPEAT_,1)( TTL_TPARAM_END,T) 這個宏體看起來很複雜,仔細分析下,可以分爲兩部分: TTL_APPEND( TTL_REPEAT_, TTL_DEC(1))(TTL_TPARAM,TTL_TPARAM_END,T)以及 TTL_APPEND( TTL_LAST_REPEAT_,1)(TTL_TPARAM_END,T) 先分析第一部分: #define TTL_APPEND( x, y ) TTL_APPEND1(x,y) //先展開x,y再將x,y連接起來 #define TTL_APPEND1( x, y ) x ## y #define TTL_DEC(n) TTL_APPEND(TTL_CNTDEC_, n) 根據先展開參數的原則,會先展開TTL_DEC(1) => TTL_APPEND(TTL_CNTDEC_,1) => TTL_CNTDEC_1 #define TTL_CNTDEC_1 0 注意,TTL_CNTDEC_不是宏,TTL_CNTDEC_1是一個宏。 => 0 , 也就是說,TTL_DEC(1)最終被展開爲0。回到TTL_APPEND部分: => TTL_REPEAT_0 (TTL_TPARAM,TTL_TPARAM_END,T) #define TTL_REPEAT_0(m,l,p) TTL_REPEAT_0這個宏爲空,那麼,上面說的第一部分被忽略,現在只剩下第二部分: TTL_APPEND( TTL_LAST_REPEAT_,1)(TTL_TPARAM_END,T) => TTL_LAST_REPEAT_1 (TTL_TPARAM_END,T) // TTL_APPEND將TTL_LAST_REPEAT_和1合併起來 #define TTL_LAST_REPEAT_1(m,p) m(1,p) => TTL_TPARAM_END( 1, T ) #define TTL_TPARAM_END(n,t) typename t##n => typename T1 展開完畢。
雖然我們分析出來了,但是這其實並不是我們想要的。我們應該從那些宏裏去獲取作者關於宏的編程思想。很好地使用宏 看上去似乎是一些偏門的奇技淫巧,但是他確實可以讓我們編碼更自動化。
|