我如何使一个C ++macros的行为像一个函数?

假设出于某种原因,您需要编写一个macros: MACRO(X,Y)(让我们假设你有一个很好的理由,你不能使用内联函数。)你希望这个macros模拟一个没有返回值的函数的调用。


示例1:这应该按预期工作。

 if (x > y) MACRO(x, y); do_something(); 

例2:这不应该导致编译器错误。

 if (x > y) MACRO(x, y); else MACRO(y - x, x - y); 

例3:这不应该编译。

 do_something(); MACRO(x, y) do_something(); 

编写macros的天真的方式是这样的:

 #define MACRO(X,Y) \ cout << "1st arg is:" << (X) << endl; \ cout << "2nd arg is:" << (Y) << endl; \ cout << "Sum is:" << ((X)+(Y)) << endl; 

这是一个非常糟糕的解决scheme,这三个例子都失败了,我不需要解释为什么。

忽略macros观实际上做了什么,那不是重点。


现在,我经常看到写入macros的方式是将它们括在大括号中,如下所示:

 #define MACRO(X,Y) \ { \ cout << "1st arg is:" << (X) << endl; \ cout << "2nd arg is:" << (Y) << endl; \ cout << "Sum is:" << ((X)+(Y)) << endl; \ } 

这解决了示例1,因为macros在一个语句块中。 但是示例2中断了,因为我们在调用macros之后放了一个分号。 这使得编译器认为分号本身就是一个语句,这意味着else语句不对应任何if语句! 最后,例3编译好了,即使没有分号,因为一个代码块不需要分号。


有没有办法写一个macros,让它通过所有三个例子?


注意:我正在提交自己的答案作为分享小费的可接受方式的一部分,但是如果有人有更好的解决方法随时在这里发布,它可能会比我的方法得到更多的选票。 🙂

一般应避免使用macros; 在任何时候都更喜欢内联函数。 任何值得使用的编译器都应该能够内联一个小函数,就好像它是一个macros,而内联函数将尊重命名空间和其他作用域,以及一次评估所有参数。

如果它必须是一个macros,那么while循环(已经build议)将起作用,或者你可以尝试逗号操作符:

 #define MACRO(X,Y) \ ( \ (cout << "1st arg is:" << (X) << endl), \ (cout << "2nd arg is:" << (Y) << endl), \ (cout << "3rd arg is:" << ((X) + (Y)) << endl), \ (void)0 \ ) 

(void)0使语句评估为voidtypes之一,并且使用逗号而不是分号允许在语句中使用它,而不是仅作为独立语句使用。 我仍然会推荐一个内联函数,其中至less有一个是范围, MACRO(a++, b++)将会增加ab两次。

有一个相当聪明的解决scheme:

 #define MACRO(X,Y) \ do { \ cout << "1st arg is:" << (X) << endl; \ cout << "2nd arg is:" << (Y) << endl; \ cout << "Sum is:" << ((X)+(Y)) << endl; \ } while (0) 

现在你有一个单独的块级语句,它必须跟一个分号。 这在所有三个例子中performance如预期和期望的那样。

我知道你说的是“忽略macros是干什么的”,但是人们会根据标题进行search来find这个问题,所以我认为讨论进一步的技术来模拟macros的function是有必要的。

最近我知道的是:

 #define MACRO(X,Y) \ do { \ auto MACRO_tmp_1 = (X); \ auto MACRO_tmp_2 = (Y); \ using std::cout; \ using std::endl; \ cout << "1st arg is:" << (MACRO_tmp_1) << endl; \ cout << "2nd arg is:" << (MACRO_tmp_2) << endl; \ cout << "Sum is:" << (MACRO_tmp_1 + MACRO_tmp_2) << endl; \ } while(0) 

这样做如下:

  • 在每个陈述的上下文中正确地工作。
  • 对它的每个参数进行一次求值,这是函数调用的一个保证特性(假设在这两种情况下,在任何一个expression式中都没有例外)。
  • 在任何types的行为,通过使用从C ++ 0x“自动”。 这还不是标准的C ++,但是没有其他方法可以获得单一评估规则所需的tmpvariables。
  • 不需要调用者从命名空间标准input名称,这是原来的macros,但一个函数不会。

但是,它仍然不同于以下function:

  • 在一些无效的使用中,它可能会给出不同的编译器错误或警告。
  • 如果X或Y包含来自周围范围的“MACRO_tmp_1”或“MACRO_tmp_2”的使用,则会出错。
  • 与命名空间相关的东西:函数使用自己的词法上下文来查找名称,而macros使用其调用站点的上下文。 在这方面,没有办法编写一个类似于函数的macros。
  • 它不能用作void函数的返回expression式,这是一个voidexpression式(如逗号解决scheme)可以使用的。 当期望的返回types不是无效时,这更是一个问题,特别是当用作左值时。 但是,逗号解决scheme不能包含使用声明,因为它们是声明,所以select一个或使用({…})GNU扩展。

这是一个来自libc6的答案! 看看/usr/include/x86_64-linux-gnu/bits/byteswap.h ,我发现你正在寻找的技巧。

以前的解决scheme的一些批评者:

  • 基普的解决scheme不允许评估expression式 ,这往往是需要的。
  • coppro的解决scheme不允许分配variables,因为expression式是分开的,但是可以计算为expression式。
  • Steve Jessop的解决scheme使用C ++ 11 auto关键字,这很好,但可以随意使用已知/预期的types

诀窍是使用(expr,expr)构造和一个{}作用域:

 #define MACRO(X,Y) \ ( \ { \ register int __x = static_cast<int>(X), __y = static_cast<int>(Y); \ std::cout << "1st arg is:" << __x << std::endl; \ std::cout << "2nd arg is:" << __y << std::endl; \ std::cout << "Sum is:" << (__x + __y) << std::endl; \ __x + __y; \ } \ ) 

注意使用register关键字,这只是编译器的一个提示。 XYmacros参数(已经)被包围在括号中,并转换为预期的types。 此解决scheme适用于前后增量,因为参数只能评估一次。

为了示例的目的,即使没有要求,我添加了__x + __y; 声明,这是使整个集团被评估为精确expression的方式。

使用void();更安全void(); 如果你想确保这个macros不会评估一个expression式,那么在一个rvalue被预期的情况下是非法的。

但是 ,解决scheme不是ISO C ++兼容,因为会抱怨g++ -pedantic

 warning: ISO C++ forbids braced-groups within expressions [-pedantic] 

为了给g++一些rest,使用(__extension__ OLD_WHOLE_MACRO_CONTENT_HERE)以便新定义读取:

 #define MACRO(X,Y) \ (__extension__ ( \ { \ register int __x = static_cast<int>(X), __y = static_cast<int>(Y); \ std::cout << "1st arg is:" << __x << std::endl; \ std::cout << "2nd arg is:" << __y << std::endl; \ std::cout << "Sum is:" << (__x + __y) << std::endl; \ __x + __y; \ } \ )) 

为了进一步改进我的解决scheme,我们使用__typeof__关键字,如MIN和MAX在C :

 #define MACRO(X,Y) \ (__extension__ ( \ { \ __typeof__(X) __x = (X); \ __typeof__(Y) __y = (Y); \ std::cout << "1st arg is:" << __x << std::endl; \ std::cout << "2nd arg is:" << __y << std::endl; \ std::cout << "Sum is:" << (__x + __y) << std::endl; \ __x + __y; \ } \ )) 

现在编译器将确定适当的types。 这也是一个gcc扩展。

请注意删除了register关键字,因为在与类types一起使用时会出现以下警告:

 warning: address requested for '__x', which is declared 'register' [-Wextra] 

C ++ 11给我们带来了lambda,在这种情况下这可能非常有用:

 #define MACRO(X,Y) \ [&](x_, y_) { \ cout << "1st arg is:" << x_ << endl; \ cout << "2nd arg is:" << y_ << endl; \ cout << "Sum is:" << (x_ + y_) << endl; \ }((X), (Y)) 

你保持macros的生成能力,但有一个舒适的范围,你可以返回任何你想要的(包括void )。 此外,避免了多次评估macros观参数的问题。

使用创build一个块

  #define MACRO(...) do { ... } while(false) 

不要添加一个; 之后(假)

你的答案遭受多重评估问题,所以(例如)

 macro( read_int(file1), read_int(file2) ); 

会做一些意想不到的事情,可能不需要。

正如其他人所说,你应该尽可能地避免macros。 如果macros观参数不止一次被评估,它们在存在副作用的情况下是危险的。 如果您知道参数的types(或者可以使用C ++ 0x autofunction),则可以使用临时对象执行单个评估。

另一个问题:多次评估的顺序可能不是你所期望的!

考虑这个代码:

 #include <iostream> using namespace std; int foo( int & i ) { return i *= 10; } int bar( int & i ) { return i *= 100; } #define BADMACRO( X, Y ) do { \ cout << "X=" << (X) << ", Y=" << (Y) << ", X+Y=" << ((X)+(Y)) << endl; \ } while (0) #define MACRO( X, Y ) do { \ int x = X; int y = Y; \ cout << "X=" << x << ", Y=" << y << ", X+Y=" << ( x + y ) << endl; \ } while (0) int main() { int a = 1; int b = 1; BADMACRO( foo(a), bar(b) ); a = 1; b = 1; MACRO( foo(a), bar(b) ); return 0; } 

它的编译和运行在我的机器上输出:

 X = 100,Y = 10000,X + Y = 110
 X = 10,Y = 100,X + Y = 110

如果你愿意采用在if语句中总是使用花括号的做法,

你的macros只是缺less最后一个分号:

 #define MACRO(X,Y) \ cout << "1st arg is:" << (X) << endl; \ cout << "2nd arg is:" << (Y) << endl; \ cout << "Sum is:" << ((X)+(Y)) << endl 

例1 :(编译)

 if (x > y) { MACRO(x, y); } do_something(); 

例2 :(编译)

 if (x > y) { MACRO(x, y); } else { MACRO(y - x, x - y); } 

例3 :(不编译)

 do_something(); MACRO(x, y) do_something();