如何使 C++ 宏表现得像一个函数?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/163365/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 13:23:29  来源:igfitidea点击:

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

c++c-preprocessor

提问by Kip

Let's say that for some reason you need to write a macro: MACRO(X,Y). (Let's assume there's a good reason you can't use an inline function.)You want this macro to emulate a call to a function with no return value.

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



Example 1: This should work as expected.

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

if (x > y)
  MACRO(x, y);
do_something();

Example 2: This should not result in a compiler error.

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

if (x > y)
  MACRO(x, y);
else
  MACRO(y - x, x - y);

Example 3: This should notcompile.

实施例3:这应该编译。

do_something();
MACRO(x, y)
do_something();


The na?ve way to write the macro is like this:

写宏的天真方法是这样的:

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

This is a very bad solution which fails all three examples, and I shouldn't need to explain why.

这是一个非常糟糕的解决方案,它在所有三个示例中都失败了,我不需要解释原因。

Ignore what the macro actually does, that's not the point.

忽略宏的实际作用,这不是重点。



Now, the way I most often see macros written is to enclose them in curly braces, like this:

现在,我最常看到编写的宏的方式是将它们括在花括号中,如下所示:

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

This solves example 1, because the macro is in one statement block. But example 2 is broken because we put a semicolon after the call to the macro. This makes the compiler think the semicolon is a statement by itself, which means the else statement doesn't correspond to any if statement! And lastly, example 3 compiles OK, even though there is no semicolon, because a code block doesn't need a semicolon.

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



Is there a way to write a macro so that it pass all three examples?

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



Note: I am submitting my own answer as part of the accepted way of sharing a tip, but if anyone has a better solution feel free to post it here, it may get more votes than my method. :)

注意:我正在提交我自己的答案,作为公认的分享技巧的一部分,但如果有人有更好的解决方案,请随时在此处发布,它可能会比我的方法获得更多的选票。:)

采纳答案by coppro

Macros should generally be avoided; prefer inline functions to them at all times. Any compiler worth its salt should be capable of inlining a small function as if it were a macro, and an inline function will respect namespaces and other scopes, as well as evaluating all the arguments once.

通常应避免使用宏;始终比他们更喜欢内联函数。任何称职的编译器都应该能够像宏一样内联一个小函数,并且内联函数将尊重命名空间和其他范围,并且对所有参数进行一次评估。

If it must be a macro, a while loop (already suggested) will work, or you can try the comma operator:

如果它必须是一个宏,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 \
 )

The (void)0causes the statement to evaluate to one of voidtype, and the use of commas rather than semicolons allows it to be used inside a statement, rather than only as a standalone. I would still recommend an inline function for a host of reasons, the least of which being scope and the fact that MACRO(a++, b++)will increment aand btwice.

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

回答by Kip

There is a rather clever solution:

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

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

Now you have a single block-level statement, which must be followed by a semicolon. This behaves as expected and desired in all three examples.

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

回答by Steve Jessop

I know you said "ignore what the macro does", but people will find this question by searching based on the title, so I think discussion of further techniques to emulate functions with macros are warranted.

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

Closest I know of is:

我所知道的最接近的是:

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

This does the following:

这将执行以下操作:

  • Works correctly in each of the stated contexts.
  • Evaluates each of its arguments exactly once, which is a guaranteed feature of a function call (assuming in both cases no exceptions in any of those expressions).
  • Acts on any types, by use of "auto" from C++0x. This is not yet standard C++, but there's no other way to get the tmp variables necessitated by the single-evaluation rule.
  • Doesn't require the caller to have imported names from namespace std, which the original macro does, but a function would not.
  • 在每个所述的上下文中都能正常工作。
  • 只计算它的每个参数一次,这是函数调用的保证特征(假设在这两种情况下,任何表达式都没有异常)。
  • 通过使用 C++0x 中的“auto”,作用于任何类型。这还不是标准的 C++,但没有其他方法可以获取单一评估规则所需的 tmp 变量。
  • 不需要调用者从命名空间 std 中导入名称,原始宏会这样做,但函数不会。

However, it still differs from a function in that:

但是,它与函数的不同之处在于:

  • In some invalid uses it may give different compiler errors or warnings.
  • It goes wrong if X or Y contain uses of 'MACRO_tmp_1' or 'MACRO_tmp_2' from the surrounding scope.
  • Related to the namespace std thing: a function uses its own lexical context to look up names, whereas a macro uses the context of its call site. There's no way to write a macro that behaves like a function in this respect.
  • It can't be used as the return expression of a void function, which a void expression (such as the comma solution) can. This is even more of an issue when the desired return type is not void, especially when used as an lvalue. But the comma solution can't include using declarations, because they're statements, so pick one or use the ({ ... }) GNU extension.
  • 在一些无效的使用中,它可能会给出不同的编译器错误或警告。
  • 如果 X 或 Y 包含来自周围范围的 'MACRO_tmp_1' 或 'MACRO_tmp_2' 的使用,则会出错。
  • 与命名空间 std 相关:函数使用自己的词法上下文来查找名称,而宏使用其调用站点的上下文。没有办法编写一个在这方面表现得像函数的宏。
  • 它不能用作void函数的返回表达式,而void表达式(例如逗号解决方案)可以。当所需的返回类型不是 void 时,尤其是用作左值时,这甚至是一个问题。但是逗号解决方案不能包括 using 声明,因为它们是语句,所以选择一个或使用 ({ ... }) GNU 扩展。

回答by ofavre

Here is an answer coming right from the libc6! Taking a look at /usr/include/x86_64-linux-gnu/bits/byteswap.h, I found the trick you were looking for.

这是来自libc6! 看一看/usr/include/x86_64-linux-gnu/bits/byteswap.h,我找到了你要找的把戏。

A few critics of previous solutions:

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

  • Kip's solution does not permit evaluating to an expression, which is in the end often needed.
  • coppro's solution does not permit assigning a variableas the expressions are separate, but can evaluate to an expression.
  • Steve Jessop's solution uses the C++11 autokeyword, that's fine, but feel free to use the known/expected typeinstead.
  • Kip 的解决方案不允许对表达式进行求值,而这最终经常需要。
  • coppro 的解决方案不允许分配变量,因为表达式是分开的,但可以计算为表达式。
  • Steve Jessop 的解决方案使用 C++11auto关键字,这很好,但可以随意使用已知/预期类型

The trick is to use both the (expr,expr)construct and a {}scope:

诀窍是同时使用(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; \
    } \
  )

Note the use of the registerkeyword, it's only a hint to the compiler. The Xand Ymacro parameters are (already) surrounded in parenthesis and castedto an expected type. This solution works properly with pre- and post-increment as parameters are evaluated only once.

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

For the example purpose, even though not requested, I added the __x + __y;statement, which is the way to make the whole bloc to be evaluated as that precise expression.

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

It's safer to use void();if you want to make sure the macro won't evaluate to an expression, thus being illegal where an rvalueis expected.

void();如果您想确保宏不会计算为表达式,则使用更安全,因此在rvalue预期的地方是非法的。

However, the solution is not ISO C++ compliantas will complain g++ -pedantic:

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

warning: ISO C++ forbids braced-groups within expressions [-pedantic]

In order to give some rest to g++, use (__extension__ OLD_WHOLE_MACRO_CONTENT_HERE)so that the new definition reads:

为了给 一些休息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; \
    } \
  ))

In order to improve my solution even a bit more, let's use the __typeof__keyword, as seen in MIN and MAX in C:

为了进一步改进我的解决方案,让我们使用__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; \
    } \
  ))

Now the compiler will determine the appropriate type. This too is a gccextension.

现在编译器将确定适当的类型。这也是一种gcc延伸。

Note the removal of the registerkeyword, as it would the following warning when used with a class type:

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

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

回答by Quentin

C++11 brought us lambdas, which can be incredibly useful in this situation:

C++11 给我们带来了 lambdas,它在这种情况下非常有用:

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

You keep the generative power of macros, but have a comfy scope from which you can return whatever you want (including void). Additionally, the issue of evaluating macro parameters multiple times is avoided.

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

回答by Andrew Stein

Create a block using

使用创建块

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

Do not add a ; after the while(false)

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

回答by Andrew Stein

Your answer suffers from the multiple-evaluation problem, so (eg)

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

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

will do something unexpected and probably unwanted.

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

回答by jwfearn

As others have mentioned, you should avoid macros whenever possible. They are dangerous in the presence of side effects if the macro arguments are evaluated more than once. If you know the type of the arguments (or can use C++0x autofeature), you could use temporaries to enforce single evaluation.

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

Another problem: the order in which multiple evaluations happen may not be what you expect!

另一个问题:多个评估发生的顺序可能不是您期望的!

Consider this code:

考虑这个代码:

#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;
}

And it's output as compiled and run on my machine:

它是在我的机器上编译并运行的输出:

X=100, Y=10000, X+Y=110
X=10, Y=100, X+Y=110

回答by Ates Goral

If you're willing to adopt the practice of always using curly braces in your if statements,

如果您愿意采用在 if 语句中始终使用花括号的做法,

Your macro would simply be missing the last semicolon:

您的宏只会缺少最后一个分号:

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

Example 1: (compiles)

示例 1:(编译)

if (x > y) {
    MACRO(x, y);
}
do_something();

Example 2: (compiles)

示例 2:(编译)

if (x > y) {
    MACRO(x, y);
} else {
    MACRO(y - x, x - y);
}

Example 3: (doesn't compile)

例3:(不编译)

do_something();
MACRO(x, y)
do_something();