在我们的遗留代码中,以及我们的现代代码中,我们使用宏来执行代码生成等的漂亮解决方案.我们同时使用#
和##
运算符.
我很好奇其他开发人员如何使用宏来做很酷的事情,如果他们根本使用它们的话.
在C中,通常定义宏来做一些获取逐字参数的东西,同时定义函数以便能够透明地获取它的地址.
// could evaluate at compile time if __builtin_sin gets // special treatment by the compiler #define sin(x) __builtin_sin(x) // parentheses avoid substitution by the macro double (sin)(double arg) { return sin(arg); // uses the macro } int main() { // uses the macro printf("%f\n", sin(3.14)); // uses the function double (*x)(double) = &sin; // uses the function printf("%f\n", (sin)(3.14)); }
最酷的宏是:断言,包括守卫,__ FILE __,__LINE__.
避免在代码中使用其他宏.
编辑:
只有在没有合法解决方案的情况下才使用宏.
还有X Macro习惯用法,它可用于DRY和简单的代码生成:
一个使用尚未定义的宏在头部gen.xa类中定义:
/** 1st arg is type , 2nd is field name , 3rd is initial value , 4th is help */ GENX( int , "y" , 1 , "number of ..." ); GENX( float , "z" , 6.3 , "this value sets ..." ); GENX( std::string , "name" , "myname" , "name of ..." );
然后他可以在不同的地方使用它,为每个#include定义它,通常有不同的定义:
class X { public : void setDefaults() { #define GENX( type , member , value , help )\ member = value ; #include "gen.x" #undef GENX } void help( std::ostream & o ) { #define GENX( type , member , value , help )\ o << #member << " : " << help << '\n' ; #include "gen.x" #undef GENX } private : #define GENX( type , member , value , help )\ type member ; #include "gen.x" #undef GENX }
您可以查看Boost.Preprocessor以查找预处理器的许多有趣用途......
用于调试的SHOW():
#define SHOW(X) cout << # X " = " << (X) << endl
扩展参数技巧的双重评估:(例如使用实际行号而不是"__LINE__".)
/* Use CONCATENATE_AGAIN to expand the arguments to CONCATENATE */ #define CONCATENATE( x,y) CONCATENATE_AGAIN(x,y) #define CONCATENATE_AGAIN(x,y) x ## y
静态编译时断言.
例如:
#define CONCATENATE_4( a,b,c,d) CONCATENATE_4_AGAIN(a,b,c,d) #define CONCATENATE_4_AGAIN(a,b,c,d) a ## b ## c ## d /* Creates a typedef that's legal/illegal depending on EXPRESSION. * * Note that IDENTIFIER_TEXT is limited to "[a-zA-Z0-9_]*". * * (This may be replaced by static_assert() in future revisions of C++.) */ #define STATIC_ASSERT( EXPRESSION, IDENTIFIER_TEXT) \ typedef char CONCATENATE_4( static_assert____, IDENTIFIER_TEXT, \ ____failed_at_line____, __LINE__ ) \ [ (EXPRESSION) ? 1 : -1 ]
用于:
typedef int32_t int4; STATIC_ASSERT( sizeof(int4) == 4, sizeof_int4_equal_4 );
初始化类CodeLocation的实例:(从调用点存储文件/行/函数 - 这只能*通过宏或通过直接访问源点处的__FILE __/__ LINE __/etc宏来完成.)
/* Note: Windows may have __FUNCTION__. C99 defines __func__. */ #define CURRENT_CODE_LOCATION() \ CodeLocation( __PRETTY_FUNCTION__, __FILE__, __LINE__ )
随后由MESSAGE/WARN/FAIL宏用作方便的源位置打印机制.例如:
#define WARN_IF_NAN(X) \ do \ { \ if ( isnan(X) != 0 ) \ WARN( # X " is NaN (Floating Point NOT-A-NUMBER)" ); \ if ( isinf(X) != 0 ) \ WARN( # X " is INF (Floating Point INFINITY)" ); \ } while ( false )
断言/除非宏.您可以通过宏传递任何令牌,包括'=='等运算符.所以构造如下:
ASSERT( foo, ==, bar )
要么
UNLESS( foo, >=, 0, value=0; return false; );
合法.断言/除非宏可以自动添加各种有用的信息,如CodeLocation,堆栈跟踪,或优雅地抛出异常/ coredumping/exiting.
使errno更简单:
#define ERRNO_FORMAT "errno= %d (\"%s\")" #define ERRNO_ARGS errno, strerror(errno) #define ERRNO_STREAM "errno= " << errno << " (\"" << strerror(errno) << "\") "
例如printf("Open failed."ERRNO_FORMAT,ERRNO_ARGS);
我最喜欢的技巧之一是将可变数量的参数传递给宏,以便稍后用于调用类似printf的函数.为此,我指定宏只有一个参数并在没有()的宏体中使用它,但是将所有参数传递给((和)中的宏),因此列表看起来像一个参数.例如,
#define TRACE( allargs) do { printf allargs; } while ( 0) ... TRACE(( "%s %s\n", "Help", "me"));
我相信Sean Barrett这个有趣的一个:
#ifndef blah #define blah(x) // something fun #include __FILE__ #undef blah #endif #ifndef blah #define blah(x) // something else that is also fun #include __FILE__ #undef blah #endif #ifdef blah blah(foo) blah(bar) #endif
一种hacky方法,让预处理器根据您可以通过宏表达的更高级别的结构为您生成代码.
记录是经常使用宏的地方之一:
#define LOG(log) \ if (!log.enabled()) {} \ else log.getStream() << __FILE__ << "@" << __LINE__ << ": " log_t errorlog; ... LOG(errorlog) << "This doesn't look good:" << somedata;
对于嵌入式代码,从一个好的技巧embeddedgurus.com 可以处理的二进制值:
B8(01010101) // 85 B16(10101010,01010101) // 43,605 B32(10000000,11111111,10101010,01010101) // 2,164,238,93
这实现了类似于@Ferruccio之前关于BOOST_BINARY的响应的类似目标,尽管有点扩展.
这是代码(copy'n粘贴,未经测试,请参阅链接了解更多详情)
// Internal Macros #define HEX__(n) 0x##n##LU #define B8__(x) ((x&0x0000000FLU)?1:0) \ +((x&0x000000F0LU)?2:0) \ +((x&0x00000F00LU)?4:0) \ +((x&0x0000F000LU)?8:0) \ +((x&0x000F0000LU)?16:0) \ +((x&0x00F00000LU)?32:0) \ +((x&0x0F000000LU)?64:0) \ +((x&0xF0000000LU)?128:0) // User-visible Macros #define B8(d) ((unsigned char)B8__(HEX__(d))) #define B16(dmsb,dlsb) (((unsigned short)B8(dmsb)<<8) + B8(dlsb)) #define B32(dmsb,db2,db3,dlsb) \ (((unsigned long)B8(dmsb)<<24) \ + ((unsigned long)B8(db2)<<16) \ + ((unsigned long)B8(db3)<<8) \ + B8(dlsb))
我喜欢宏.调试时非常有趣!
我使用宏的主要地方是我自己的测试框架.例如,当我想断言某些代码必须抛出时,我使用这个宏:
#define MUST_THROW( expr ) try { (expr); (myth_suite_).Fail( #expr + std::string( " should throw but didn't" ) ); } catch( ... ) { }
并像这样使用它:
MUST_THROW( some_bogus_stuff() ); MUST_THROW( more_bogus_stuff() );
我使用它们的唯一其他地方是在类声明中.我有一个宏:
#define CANNOT_COPY( cls ) \ private: \ cls( const cls & ); \ void operator=( const cls & ) \
我用它来指定一个类不能被复制(或分配):
class BankAccount { CANNOT_COPY( BankAccount ); .... };
这没有做任何特别的事情,但引起了人们的注意,可以很容易地进行搜索.