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

Kip*_*Kip 48 c++ c-preprocessor

让我们说由于某种原因你需要写一个宏:MACRO(X,Y). (我们假设您有一个很好的理由不能使用内联函数.) 您希望此宏模拟对没有返回值的函数的调用.


示例1:这应该按预期工作.

if (x > y)
  MACRO(x, y);
do_something();
Run Code Online (Sandbox Code Playgroud)

示例2:这不应导致编译器错误.

if (x > y)
  MACRO(x, y);
else
  MACRO(y - x, x - y);
Run Code Online (Sandbox Code Playgroud)

实施例3:这应该编译.

do_something();
MACRO(x, y)
do_something();
Run Code Online (Sandbox Code Playgroud)

编写宏的天真方式是这样的:

#define MACRO(X,Y)                       \
cout << "1st arg is:" << (X) << endl;    \
cout << "2nd arg is:" << (Y) << endl;    \
cout << "Sum is:" << ((X)+(Y)) << endl;
Run Code Online (Sandbox Code Playgroud)

这是一个非常糟糕的解决方案,它失败了所有三个例子,我不应该解释原因.

忽略宏实际上做的事情,这不是重点.


现在,我经常看到编写宏的方法是将它们用大括号括起来,如下所示:

#define MACRO(X,Y)                         \
{                                          \
  cout << "1st arg is:" << (X) << endl;    \
  cout << "2nd arg is:" << (Y) << endl;    \
  cout << "Sum is:" << ((X)+(Y)) << endl;  \
}
Run Code Online (Sandbox Code Playgroud)

这解决了示例1,因为宏位于一个语句块中.但是示例2被打破了,因为我们在调用宏之后放了一个分号.这使编译器认为分号本身就是一个语句,这意味着else语句不对应任何if语句!最后,示例3编译好,即使没有分号,因为代码块不需要分号.


有没有办法写一个宏,以便通过所有三个例子?


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

Kip*_*Kip 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)
Run Code Online (Sandbox Code Playgroud)

现在你有一个块级语句,后面必须跟一个分号.这在所有三个示例中表现得如预期和期望的那样.

  • 是的,我们都知道调用具有复杂参数的宏的缺陷.这就是为什么每个人都(或应该!)在所有大写中命名宏,所以你知道不要做任何愚蠢的事情. (7认同)
  • "MACRO(++ x, - y)"怎么样? (3认同)
  • 多重评估问题并非不可克服,顺便说一句。检查一个聪明人所做的代码-&gt; http://pastie.org/357910 它提供了一个万无一失的“RANGE(foo)”宏,基本上生成“foo.begin(), foo.end()”,但它是适用的用于解决任何多重评估。但是,运行时开销。 (2认同)

cop*_*pro 42

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

如果它必须是宏,则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 \
 )
Run Code Online (Sandbox Code Playgroud)

(void)0原因的声明,以评估的一个void类型,使用逗号而不是分号允许它的声明内使用,而不是仅仅作为一个独立的.我仍然会推荐一个内联函数的理由有很多,这是范围,并且这一事实至少MACRO(a++, b++)将增加ab的两倍.

  • 函数还具有优越性,因为编译器可以决定是否内联它们,而对于宏则别无选择。内联通常不是人们期望的性能胜利。 (3认同)
  • 但是,如果需要执行一条语句然后返回一个值,则此方法很有用。例如,我已使用它通过[获取语义](http://preshing.com/20120913/acquire-and-release-semantics)在负载之前放置了内存屏障。 (2认同)

Ste*_*sop 18

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

我知道的最近的是:

#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)
Run Code Online (Sandbox Code Playgroud)

这样做如下:

  • 在每个指定的上下文中正确工作.
  • 准确地计算每个参数一次,这是函数调用的保证特性(假设在这两种情况下都没有异常).
  • 通过使用C++ 0x中的"auto"对任何类型进行操作.这还不是标准的C++,但没有其他方法可以获得单一评估规则所必需的tmp变量.
  • 不需要调用者从命名空间std导入名称,原始宏执行,但函数不会.

但是,它仍然不同于以下功能:

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


ofa*_*vre 13

这是一个来自的答案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; \
    } \
  )
Run Code Online (Sandbox Code Playgroud)

注意使用register关键字,它只是对编译器的一个提示.的XY宏观参数是包围在括号中和(已经)浇铸到一个预期的类型.此解决方案适用于前后增量,因为参数仅评估一次.

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

void();如果你想确保宏不会评估表达式,那么使用它会更安全,因此在rvalue预期的情况下是非法的.

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

warning: ISO C++ forbids braced-groups within expressions [-pedantic]
Run Code Online (Sandbox Code Playgroud)

为了给予一些休息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; \
    } \
  ))
Run Code Online (Sandbox Code Playgroud)

为了更好地改进我的解决方案,让我们使用__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; \
    } \
  ))
Run Code Online (Sandbox Code Playgroud)

现在编译器将确定适当的类型.这也是一个gcc扩展.

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

warning: address requested for ‘__x’, which is declared ‘register’ [-Wextra]
Run Code Online (Sandbox Code Playgroud)


Que*_*tin 7

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))
Run Code Online (Sandbox Code Playgroud)

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


And*_*ein 5

使用创建一个块

 #define MACRO(...) do { ... } while(false)
Run Code Online (Sandbox Code Playgroud)

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