我们来见识一下宏是如何帮助我们自动产生代码的。如我所说,宏是在符号层次产生代码。我在分析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 展开完毕。 虽然我们分析出来了,但是这其实并不是我们想要的。我们应该从那些宏里去获取作者关于宏的编程思想。很好地使用宏 看上去似乎是一些偏门的奇技淫巧,但是他确实可以让我们编码更自动化。 |