题
假设由于某种原因您需要编写一个宏: 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 也可以编译正常,因为代码块不需要分号。
有没有办法编写一个宏以使其通过所有三个示例?
笔记:我正在提交我自己的答案作为 接受的分享小费的方式, ,但是如果有人有更好的解决方案,请随时将其发布在这里,它可能会比我的方法获得更多的选票。:)
解决方案
一般应避免使用宏;在任何时候都喜欢内联函数。任何有价值的编译器应该能够内联一个小函数,就好像它是一个宏,内联函数将尊重名称空间和其他作用域,以及一次评估所有参数。
如果它必须是宏,则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 ++)
将增加 a
和的事实b
两次。
其他提示
有一个相当聪明的解决方案:
#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)
现在你有一个块级语句,后面必须跟一个分号。这在所有三个示例中都表现出预期和期望。
我知道你说“忽略宏的作用”,但是人们会通过根据标题进行搜索来找到这个问题,所以我认为有必要讨论使用宏来模拟函数的进一步技术。
我知道的最接近的是:
#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 扩展。
这是来自的答案 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
关键字,它只是对编译器的一个提示。这 X
和 Y
宏参数(已经)被括在括号中并且 铸造的 到预期的类型。该解决方案适用于预增量和后增量,因为参数仅评估一次。
出于示例目的,即使没有要求,我还是添加了 __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)
不要添加;过了一会儿(假)
您的答案会受到多重评估问题的影响,因此(例如)
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=110 X=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();