假设由于某种原因您需要编写一个宏: 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 也可以编译正常,因为代码块不需要分号。


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


笔记:我正在提交我自己的答案作为 接受的分享小费的方式, ,但是如果有人有更好的解决方案,请随时将其发布在这里,它可能会比我的方法获得更多的选票。:)

有帮助吗?

解决方案

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

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

其他提示

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

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

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

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

  • Kip 的解决方案不允许 评估表达式, ,这最终是经常需要的。
  • coppro 的解决方案不允许 分配变量 因为表达式是独立的,但可以计算为表达式。
  • Steve Jessop 的解决方案使用 C++11 auto 关键字,没问题,但是 随意使用已知/预期的类型 反而。

诀窍是同时使用 (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 关键字,它只是对编译器的一个提示。这 XY 宏参数(已经)被括在括号中并且 铸造的 到预期的类型。该解决方案适用于预增量和后增量,因为参数仅评估一次。

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

使用起来更安全 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]

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

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

使用

创建一个块
 #define MACRO(...) do { ... } while(false)

不要添加;过了一会儿(假)

您的答案会受到多重评估问题的影响,因此(例如)

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

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

正如其他人所说,你应该尽可能避免使用宏。如果宏参数被多次评估,它们在存在副作用时是危险的。如果您知道参数的类型(或者可以使用C ++ 0x auto 功能),则可以使用临时值来强制执行单一评估。

另一个问题:多次评估发生的顺序可能不符合您的预期!

考虑以下代码:

#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语句中始终使用花括号的做法,

你的宏只会丢失最后一个分号:

#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();
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top