【问题标题】:How do I make a C++ macro behave like a function?如何使 C++ 宏表现得像函数?
【发布时间】:2010-09-14 20:52:49
【问题描述】:

假设由于某种原因您需要编写一个宏: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 的一部分提交,但如果有人有更好的解决方案,请随时在此处发布,它可能会比我的方法获得更多的选票。 :)

【问题讨论】:

标签: c++ c-preprocessor


【解决方案1】:

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

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

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

【讨论】:

  • 是的,我们都知道使用复杂参数调用宏的陷阱。这就是为什么每个人都会(或应该!)用大写字母命名宏,所以你知道不要做任何愚蠢的事情。
  • 多重评估问题并非不可克服,顺便说一句。检查这个代码是一个聪明人所做的 --> pastie.org/357910 它提供了一个万无一失的“RANGE(foo)”宏,基本上可以生成“foo.begin(), foo.end()”,但它适用于解决任何多重评估。不过,运行时开销。
【解决方案2】:

使用创建块

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

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

      • 好极了,这比我原来做的还要好!谢谢!
      • 函数的优越性还在于编译器可以决定是否内联它们,而宏则没有选择。内联通常不是人们期望的性能提升。
      • “太好了,这比我原来做的还要好!” ——你错了基普,你的答案比这个更好。你的函数可以表现得比这个函数更多,例如,如果它需要一个循环在它的中间。
      • Kip,你的版本更好。如果您必须在宏中声明一些局部变量,在 while 块内声明它们不会使它们泄漏到调用范围内。
      • 但是,如果您需要执行一条语句然后返回一个值,这种方法很有用。例如,我用它在 acquire semantics 的负载前面放置了一个内存屏障。
      【解决方案5】:

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

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

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

      【讨论】:

        【解决方案6】:

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

        【讨论】:

        • 你的好宏的问题是你假设 X 和 Y 是 int 的。它们可以是任何东西。你需要模板化你的宏或其他东西。
        • 注意:godbolt 上的输出与答案中给出的不一致:godbolt.org/z/9fQuA9
        【解决方案7】:

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

        我所知道的最接近的是:

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

        【讨论】:

          【解决方案8】:

          这是来自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__ 关键字,如@9​​87654321@ 所示:

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

          【讨论】:

            【解决方案9】:

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

            【讨论】:

              猜你喜欢
              • 2013-07-25
              • 2011-01-23
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2023-03-15
              • 2012-11-24
              • 2017-06-19
              • 2017-01-26
              相关资源
              最近更新 更多