c 预处理器 - 如何使C++宏的行为像函数一样

c preprocessor - How do I make a C++ macro behave like a function?

本文关键字:函数 一样 处理器 预处理 何使 C++      更新时间:2023-10-16

假设由于某种原因你需要编写一个宏: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)

现在,您有一个块级语句,该语句后必须跟一个分号。 这在所有三个示例中都符合预期和期望。

通常应避免使用宏;始终首选内联函数。任何值得一提的编译器都应该能够像宏一样内联一个小函数,并且内联函数将尊重命名空间和其他范围,并一次性计算所有参数。

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

我知道你说"忽略宏的作用",但人们会通过根据标题搜索来找到这个问题,所以我认为讨论使用宏模拟函数的进一步技术是必要的。

我所知道的最接近的是:

#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 的"自动"作用于任何类型。这还不是标准C++,但没有其他方法可以获取单一评估规则所需的 tmp 变量。
  • 不要求调用方具有从命名空间 std 导入的名称,原始宏会这样做,但函数不会。

但是,它仍然与函数不同,因为:

  • 在某些无效的用法中,它可能会给出不同的编译器错误或警告。
  • 如果 X 或 Y 包含周围范围内的"MACRO_tmp_1"或"MACRO_tmp_2"的使用,则会出错。
  • 与命名空间 std 相关:函数使用自己的词法上下文来查找名称,而宏使用其调用站点的上下文。在这方面,没有办法编写一个行为类似于函数的宏。
  • 它不能用作 void 函数的返回表达式,而 void 表达式(如逗号解(可以。当所需的返回类型不为 null 时,尤其是用作左值时,这更是一个问题。但是逗号解决方案不能包含使用声明,因为它们是语句,因此请选择一个或使用 ({ ... }(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)

不要在 while(假(之后添加 ;

你的答案受到多重评估问题的影响,所以(例如(

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=110X=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();