如何使C ++宏的行为类似于函数?
假设由于某种原因,我们需要编写一个宏:MACRO(X,Y)。 (假设有充分的理由不能使用内联函数。)我们希望此宏模拟对没有返回值的函数的调用。
示例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();
编写宏的简单方法是这样的:
#define MACRO(X,Y) \ cout << "1st arg is:" << (X) << endl; \ cout << "2nd arg is:" << (Y) << endl; \ cout << "Sum is:" << ((X)+(Y)) << endl;
这是一个非常糟糕的解决方案,它无法通过所有三个示例,因此我不需要解释原因。
忽略宏实际执行的操作,这不是重点。
现在,我最经常看到的编写宏的方法是将它们括在花括号中,如下所示:
#define MACRO(X,Y) \ { \ cout << "1st arg is:" << (X) << endl; \ cout << "2nd arg is:" << (Y) << endl; \ cout << "Sum is:" << ((X)+(Y)) << endl; \ }
这解决了示例1,因为宏在一个语句块中。但是示例2被破坏了,因为我们在对宏的调用之后放置了分号。这使编译器认为分号本身就是一个语句,这意味着else语句与任何if语句都不对应!最后,即使没有分号,示例3仍可以编译,因为代码块不需要分号。
有没有一种方法可以编写宏,使其通过所有三个示例?
注意:我将提交自己的答案,作为公认的分享技巧的一部分,但是,如果有人有更好的解决方案,可以随时在此处发布,则可能会获得比我的方法更多的选票。 :)
解决方案
有一个相当聪明的解决方案:
#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)
现在,我们只有一个块级语句,必须在其后跟一个分号。这在所有三个示例中均符合预期和期望。
使用创建一个块
#define MACRO(...) do { ... } while(false)
不要添加;一会儿之后(false)
如果我们愿意在if语句中始终使用大括号的做法,
宏只会丢失最后一个分号:
#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();
通常应避免使用宏;始终喜欢内联函数。任何值得赞扬的编译器都应该能够像嵌入宏一样内联一个小函数,并且内联函数将尊重名称空间和其他作用域,并且一次评估所有参数。
如果必须是宏,则可以使用while循环(建议使用),也可以尝试使用逗号运算符:
#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)导致语句的计算结果为void
类型之一,并且使用逗号(而不是分号)允许在语句内部使用它,而不仅仅是将其用作独立语句。由于种种原因,我仍然建议使用内联函数,其中至少是范围,以及MACRO(a ++,b ++)
将a
和b
递增两次的事实。
答案存在多重评估问题,因此(例如)
macro( read_int(file1), read_int(file2) );
会做一些意想不到的事情,可能是不需要的。
正如其他人提到的那样,我们应尽可能避免使用宏。如果对宏参数进行多次评估,则在存在副作用的情况下它们很危险。如果我们知道参数的类型(或者可以使用C ++ 0x自动功能),则可以使用临时变量来执行单个求值。
另一个问题:进行多次评估的顺序可能与我们预期的不一样!
考虑以下代码:
#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
我知道我们说过"忽略宏的作用",但是人们会通过根据标题进行搜索来找到这个问题,因此我认为有必要对进一步的技术进行仿真,以使用宏来模拟函数。
我知道的最接近的是:
#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)
这将执行以下操作:
- 在每个陈述的上下文中都能正常工作。
- 仅对每个参数进行一次评估,这是函数调用的保证功能(假设在两种情况下,这些表达式中的任何一个都不例外)。
- 通过使用C ++ 0x中的" auto",可以对任何类型进行操作。这不是标准的C ++,但是没有其他方法可以获取单求值规则必需的tmp变量。
- 不需要调用者具有从名称空间std导入的名称,原始宏则需要,而函数则不需要。
但是,它仍然与功能不同之处在于:
- 在某些无效使用中,它可能会给出不同的编译器错误或者警告。
- 如果X或者Y包含来自周围范围的'MACRO_tmp_1'或者'MACRO_tmp_2'的使用,则会出错。
- 与名称空间std事物有关:函数使用其自身的词法上下文来查找名称,而宏使用其调用站点的上下文。在这方面,没有办法编写行为类似于函数的宏。
- 它不能用作void函数的返回表达式,void函数(例如逗号解决方案)可以这样做。当所需的返回类型不为空时,尤其是当用作左值时,这甚至是一个问题。但是逗号解决方案不能包括使用声明,因为它们是语句,因此请选择一个或者使用({... ...))GNU扩展。