我可以使用宏的一些技巧?

26 c c++ stringification c-preprocessor

在我们的遗留代码中,以及我们的现代代码中,我们使用宏来执行代码生成等的漂亮解决方案.我们同时使用###运算符.

我很好奇其他开发人员如何使用宏来做很酷的事情,如果他们根本使用它们的话.

Joh*_*itb 27

在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));
}
Run Code Online (Sandbox Code Playgroud)

  • jder,实际上C(和C++)标准说只有当有一个(紧跟宏名称.如果有的话)之后才能替换像宏这样的函数,就像我们的情况一样,没有替换完成:) 6.10.3/10在C99 TC2(doc n1124 here).在7.1.4中有一个例子作为脚注. (15认同)
  • 哇!什么与(罪)?你有这个预防宏扩展的参考吗?我从来没有听说过这个. (3认同)

bay*_*yda 14

最酷的宏是:断言,包括守卫,__ FILE __,__LINE__.
避免在代码中使用其他宏.

编辑:
只有在没有合法解决方案的情况下才使用宏.


小智 14

还有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 ..." );
Run Code Online (Sandbox Code Playgroud)

然后他可以在不同的地方使用它,为每个#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
}
Run Code Online (Sandbox Code Playgroud)

  • 为什么不把'#undef GENX`放在**gen.x**里面? (4认同)
  • 这个我见过很多,也用过。它并不漂亮,但对于反射可能有用的地方,它是一种很好的技术。 (2认同)

sth*_*sth 11

您可以查看Boost.Preprocessor以查找预处理器的许多有趣用途......


Mr.*_*Ree 11

用于调试的SHOW():

#define SHOW(X) cout << # X " = " << (X) << endl
Run Code Online (Sandbox Code Playgroud)

扩展参数技巧的双重评估:(例如使用实际行号而不是"__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
Run Code Online (Sandbox Code Playgroud)

静态编译时断言.
例如:

#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 ]
Run Code Online (Sandbox Code Playgroud)

用于:

typedef  int32_t  int4;

STATIC_ASSERT( sizeof(int4) == 4, sizeof_int4_equal_4 );
Run Code Online (Sandbox Code Playgroud)

初始化类CodeLocation的实例:(从调用点存储文件/行/函数 - 这只能*通过宏或通过直接访问源点处的__FILE __/__ LINE __/etc宏来完成.)

        /* Note:  Windows may have __FUNCTION__.  C99 defines __func__. */
#define CURRENT_CODE_LOCATION()  \
           CodeLocation( __PRETTY_FUNCTION__, __FILE__, __LINE__ )
Run Code Online (Sandbox Code Playgroud)

随后由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 )
Run Code Online (Sandbox Code Playgroud)

断言/除非宏.您可以通过宏传递任何令牌,包括'=='等运算符.所以构造如下:

ASSERT( foo, ==, bar )
Run Code Online (Sandbox Code Playgroud)

要么

UNLESS( foo, >=, 0, value=0; return false; );
Run Code Online (Sandbox Code Playgroud)

合法.断言/除非宏可以自动添加各种有用的信息,如CodeLocation,堆栈跟踪,或优雅地抛出异常/ coredumping/exiting.


使errno更简单:

#define ERRNO_FORMAT  "errno= %d (\"%s\")"
#define ERRNO_ARGS    errno, strerror(errno)
#define ERRNO_STREAM  "errno= " << errno << " (\"" << strerror(errno) << "\") "
Run Code Online (Sandbox Code Playgroud)

例如printf("Open failed."ERRNO_FORMAT,ERRNO_ARGS);


dmi*_*gov 8

我最喜欢的技巧之一是将可变数量的参数传递给宏,以便稍后用于调用类似printf的函数.为此,我指定宏只有一个参数并在没有()的宏体中使用它,但是将所有参数传递给((和)中的宏),因此列表看起来像一个参数.例如,

#define TRACE( allargs) do { printf allargs; } while ( 0)
...
TRACE(( "%s %s\n", "Help", "me"));
Run Code Online (Sandbox Code Playgroud)

  • 您也可以在c99中使用__VA_ARGS__,它在调用者看起来更好. (6认同)
  • 尽管我知道 VA_ARGS,但它是一个非常酷的宏用法示例。 (2认同)

MSN*_*MSN 8

我相信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
Run Code Online (Sandbox Code Playgroud)

一种hacky方法,让预处理器根据您可以通过宏表达的更高级别的结构为您生成代码.

  • +1:这真的很整齐;就像这里其他答案中描述的“ X宏惯用语”一样,只是您不需要单独的文件。 (2认同)

sth*_*sth 7

记录是经常使用宏的地方之一:

#define LOG(log) \
  if (!log.enabled()) {} \
  else log.getStream() << __FILE__ << "@" << __LINE__ << ": "


log_t errorlog;
...

LOG(errorlog) << "This doesn't look good:" << somedata;
Run Code Online (Sandbox Code Playgroud)


keb*_*ebs 7

对于嵌入式代码,从一个好的技巧embeddedgurus.com 可以处理的二进制值:

B8(01010101) // 85
B16(10101010,01010101) // 43,605
B32(10000000,11111111,10101010,01010101) // 2,164,238,93
Run Code Online (Sandbox Code Playgroud)

这实现了类似于@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))
Run Code Online (Sandbox Code Playgroud)

我喜欢宏.调试时非常有趣!


小智 6

我使用宏的主要地方是我自己的测试框架.例如,当我想断言某些代码必须抛出时,我使用这个宏:

#define MUST_THROW( expr )                       
  try {                                
    (expr);                              
    (myth_suite_).Fail( #expr +                    
            std::string( " should throw but didn't" ) );  
  }                                  
  catch( ... ) {                            
  }                                  
Run Code Online (Sandbox Code Playgroud)

并像这样使用它:

MUST_THROW( some_bogus_stuff() );
MUST_THROW( more_bogus_stuff() );
Run Code Online (Sandbox Code Playgroud)

我使用它们的唯一其他地方是在类声明中.我有一个宏:

#define CANNOT_COPY( cls )              \
  private:                              \
    cls( const cls & );                 \
    void operator=( const cls & )       \
Run Code Online (Sandbox Code Playgroud)

我用它来指定一个类不能被复制(或分配):

class BankAccount {

    CANNOT_COPY( BankAccount );
    ....
};
Run Code Online (Sandbox Code Playgroud)

这没有做任何特别的事情,但引起了人们的注意,可以很容易地进行搜索.

  • 这当然使继承方案更加复杂,当你需要一个"真正的"基类时,它会不必要地增加某些编译器上类指针的大小. (3认同)

u0b*_*6ae 6

使用 C99 可变参数宏,具有默认值(不为零)的结构体字面量

struct Example {
   int from;
   int to;
   const char *name;
}

#define EXAMPLE(...) ((struct Example){.from=0, .to=INT_MAX, .name="", __VA_ARGS__})
Run Code Online (Sandbox Code Playgroud)

usingEXAMPLE(.name="test")使用默认值,除了显式覆盖name. 后面提到的同一成员的这种阴影在标准中得到了很好的定义。

  • C 是否只是获得了关键字参数? (2认同)

epa*_*tel 5

人们可以简化重复的事情,即。枚举列表

enum {
  kOneEnum,
  kTwoEnum,
  kThreeEnum,
  kFourEnum
};
Run Code Online (Sandbox Code Playgroud)

...然后通过结构化方式执行 switch case

#define TEST( _v ) \
    case k ## _v ## Enum: \
      CallFunction ## _v(); \
      break;

switch (c) {
    TEST( One   );
    TEST( Two   );
    TEST( Three );
    TEST( Four  );
}
Run Code Online (Sandbox Code Playgroud)

注意:当然,这可以通过函数指针数组来完成,但这为添加参数以及使用单个散列的字符串扩展提供了更多的灵活性。

...或者测试字符串以获得正确的枚举值

int value = -1;
char *str = getstr();

#define TEST( _v ) \
    if (!strcmp(# _v, str)) \
        value = k ## _v ## Enum

TEST( One   );
TEST( Two   );
TEST( Three );
TEST( Four  );
Run Code Online (Sandbox Code Playgroud)


RBe*_*eig 5

我经常将调试声纳之类的东西包装在一个简单的宏中,允许它从发布版本中编译出来:

#ifdef DEBUG
#define D(s) do { s; } while(0)
#else
#define D(s) do {/**/} while(0)
#endif
Run Code Online (Sandbox Code Playgroud)

稍后使用通常类似于:

D(printf("level %d, condition %s\n", level, condition));
Run Code Online (Sandbox Code Playgroud)

do{}while(0)成语是对有可能导致意外使的使用避免出现问题D(...)有条件的或循环的唯一内容。毕竟,您不希望这样的代码意味着错误的事情:

for(i=1;i<10;++i) D(printf("x[%d]=%f\n",i,x[i]));
SomeReallyExpensiveFunction(x);
Run Code Online (Sandbox Code Playgroud)

如果我可以让这种情况抛出错误,我会,但预处理器本身必须是一个完整的编译器,才能告诉D()宏是循环体的唯一内容。

我也是编译时断言的忠实粉丝。我的配方略有不同,但与我见过的其他配方相比没有真正的优势。关键是形成一个唯一命名的 typedef,如果断言的条件为假则抛出错误,否则不会。在 cassert.h 我们有:

/*! \brief Compile-time assertion.
 *
 *  Note that the cassert() macro generates no code, and hence need not
 *  be restricted to debug builds.  It does have the side-effect of
 *  declaring a type name with typedef.  For this reason, a unique
 *  number or string of legal identifier characters must be included
 *  with each invocation to avoid the attempt to redeclare a type.
 *
 *  A failed assertion will attempt to define a type that is an array
 *  of -1 integers, which will throw an error in any standards
 *  compliant compiler. The exact error is implementation defined, but
 *  since the defined type name includes the string "ASSERTION" it
 *  should trigger curiosity enough to lead the user to the assertion
 *  itself.
 *
 *  Because a typedef is used, cassert() may be used inside a function,
 *  class or struct definition as well as at file scope.
 */
#define cassert(x,i) typedef int ASSERTION_##i[(x)?1:-1]
Run Code Online (Sandbox Code Playgroud)

在某些源文件中,任何 typedef 都是合法的:

#include "cassert.h"
...
cassert(sizeof(struct foo)==14, foo1);
...
Run Code Online (Sandbox Code Playgroud)

由此产生的错误消息通常是模糊的,但会包含标识符的片段,使违规行能够被蛮力发现。

我一直对在编写代码生成实用程序可能是首选答案的地方使用预处理器感到内疚,就像另一个答案中的代码根据枚举成员名称的唯一部分生成大量样板。这在编写大量要在 C 中编译的消息调度胶时特别方便。