如何使C ++宏的行为类似于函数?

时间:2020-03-06 15:02:12  来源:igfitidea点击:

假设由于某种原因,我们需要编写一个宏: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 ++)ab递增两次的事实。

答案存在多重评估问题,因此(例如)

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扩展。