62

假设由于某种原因您需要编写一个宏: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 编译成功,即使没有分号,因为代码块不需要分号。


有没有办法编写一个宏以使其通过所有三个示例?


注意:我提交我自己的答案作为共享提示的公认方式的一部分,但如果有人有更好的解决方案,请随时在此处发布,它可能会比我的方法获得更多的选票。:)

4

9 回答 9

50

有一个相当聪明的解决方案:

#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)

现在你有一个单独的块级语句,它后面必须跟一个分号。这在所有三个示例中都符合预期和期望。

于 2008-10-02T16:41:21.847 回答
50

通常应避免使用宏;在任何时候都更喜欢内联函数。任何称职的编译器都应该能够像宏一样内联一个小函数,并且内联函数将尊重命名空间和其他范围,以及一次评估所有参数。

如果它必须是宏,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

于 2008-10-02T16:54:59.677 回答
20

我知道您说“忽略宏的作用”,但是人们会通过根据标题进行搜索来找到这个问题,所以我认为有必要讨论进一步的技术来模拟宏的功能。

我所知道的最接近的是:

#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 表达式(例如逗号解决方案)可以。当所需的返回类型不是 void 时,这甚至会成为一个更大的问题,尤其是在用作左值时。但是逗号解决方案不能包括 using 声明,因为它们是语句,所以选择一个或使用 ({ ... }) GNU 扩展。
于 2008-10-02T23:26:25.540 回答
17

这是来自的答案libc6!看了一下/usr/include/x86_64-linux-gnu/bits/byteswap.h,我找到了你要找的把戏。

对先前解决方案的一些批评:

  • Kip 的解决方案不允许对最终经常需要的表达式求值。
  • coppro 的解决方案不允许分配变量,因为表达式是分开的,但可以评估为表达式。
  • Steve Jessop 的解决方案使用 C++11auto关键字,这很好,但可以随意使用已知/预期类型

诀窍是同时使用(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关键字的使用,它只是对编译器的提示。和宏参数(已经)用括号括起来并X转换预期的类型。此解决方案适用于前增量和后增量,因为参数只评估一次。Y

出于示例目的,即使没有要求,我也添加了__x + __y;语句,这是使整个 bloc 被评估为精确表达式的方法。

void();如果您想确保宏不会评估为表达式,那么使用它会更安全,因此在rvalue预期的地方是非法的。

但是,该解决方案不符合 ISO C++ 标准,因为会抱怨g++ -pedantic

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

为了让 休息一下g++,使用(__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; \
    } \
  ))

为了进一步改进我的解决方案,让我们使用__typeof__关键字,如C 中的 MIN 和 MAX所示:

#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; \
    } \
  ))

现在编译器将确定适当的类型。这也是一种gcc延伸。

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

warning: address requested for ‘__x’, which is declared ‘register’ [-Wextra]
于 2011-12-21T13:36:58.787 回答
10

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))

You keep the generative power of macros, but have a comfy scope from which you can return whatever you want (including void). Additionally, the issue of evaluating macro parameters multiple times is avoided.

于 2016-11-04T13:26:04.643 回答
4

使用创建块

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

不要添加 ; 一段时间后(假)

于 2008-10-02T16:50:39.500 回答
3

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

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

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

于 2008-10-02T17:11:24.897 回答
1

正如其他人所提到的,您应该尽可能避免使用宏。如果对宏参数进行多次评估,则它们在存在副作用的情况下是危险的。如果您知道参数的类型(或可以使用 C++0xauto功能),则可以使用临时变量来强制执行单一评估。

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

考虑这段代码:

#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
于 2008-10-02T17:46:00.303 回答
0

如果您愿意采用在 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();
于 2008-10-02T16:54:33.143 回答