如何使C ++宏像函数一样?

时间:2008-10-02 16:41:14

标签: c++ c-preprocessor

让我们说由于某种原因你需要写一个宏: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编译好,即使没有分号,因为代码块不需要分号。


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


注意:我作为accepted way of sharing a tip的一部分提交了我自己的答案,但如果有人有更好的解决方案,请随时在此发布,它可能会得到比我的方法更多的选票。 :)

9 个答案:

答案 0 :(得分:43)

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

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

现在你有一个块级语句,后面必须跟一个分号。这在所有三个示例中都表现出预期和期望。

答案 1 :(得分:40)

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

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

答案 2 :(得分:17)

我知道你说“忽略宏的作用”,但是人们会通过基于标题的搜索找到这个问题,所以我认为有必要讨论用宏来模拟函数的其他技术。

我知道的最近的是:

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

答案 3 :(得分:12)

以下是来自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;语句,这是使整个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__关键字,如MIN and MAX in 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; \
    } \
  ))

现在编译器将确定合适的类型。这也是gcc扩展名。

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

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

答案 4 :(得分:6)

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)。此外,避免了多次评估宏参数的问题。

答案 5 :(得分:3)

使用

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

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

答案 6 :(得分:2)

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

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

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

答案 7 :(得分:0)

正如其他人所说,你应该尽可能避免使用宏。如果宏参数被多次评估,它们在存在副作用时是危险的。如果您知道参数的类型(或者可以使用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

答案 8 :(得分:-2)

如果您愿意采用在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();