c 预处理器 - 如何使C++宏的行为像函数一样
c preprocessor - How do I make a C++ macro behave like a function?
假设由于某种原因你需要编写一个宏: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++)
将增加a
和b
两次的事实。
我知道你说"忽略宏的作用",但人们会通过根据标题搜索来找到这个问题,所以我认为讨论使用宏模拟函数的进一步技术是必要的。
我所知道的最接近的是:
#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
关键字的使用,它只是对编译器的提示。宏参数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)
不要在 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();
- JavaScript箭头函数:我们能否像C ++ lambdas一样捕获值
- 函数返回动态强制转换的指针,返回指针,就好像它根本没有被转换一样
- 为什么向量不支持 size 作为成员变量,就像 Java 类中的长度变量一样,而是函数 size()?
- 'inline'变量可以像内联函数一样内联吗?
- C++11: 如何编写一个像 Get 一样工作的模板函数<tuple>...但是收到参数包?
- 将参数传递给成员函数,就像使用 std::cout 一样
- 如何创建像标准库头一样的头,以便在不链接头中函数的所有对象文件的情况下编译程序?
- 模拟一个函数,该函数像操作员=和破坏者一样传播到每个字段
- 可变参数函数并复制粘贴这些参数(就像我们在宏中一样)
- 如何像函数一样调用构造函数
- 我多么应该将空指针传递给c ++函数,就像C#中的IntPter.Zero一样
- 模板成员变量像通用函数一样键入
- 可以像'funs[1]();'一样调用的函数数组
- 如何在C++中像函数一样存储参数矩阵
- 使用发出与调用信号,就好像它是Qt中的常规函数一样
- 你能像调用成员函数一样调用朋友函数吗?
- 如何像普通 C 函数一样使用正确的 'this' 指针调用 C++ 类成员函数?(指向类成员函数的指针)
- 我能让赋值运算符像复制构造函数一样工作吗
- 如何像传递未定义函数一样传递未定义方法
- c 预处理器 - 如何使C++宏的行为像函数一样