我对宏及其可读性进行了辩论。 我认为在某些情况下使用宏可以使代码更短,更易于理解,并且阅读时不那么累。
例如:
#include <iostream>
#define EXIT_ON_FAILURE(s) if(s != 0) {std::cout << "Exited on line " << __LINE__ << std::endl; exit(1);}
inline void exitOnFailure(int s, int lineNum) {
if (s != 0) {
std::cout << "Exited on line " << lineNum << std::endl;
exit(1);
}
}
int foo() {
return 1;
}
int bar(int a, int b, int c) {
return 0;
}
int main() {
// first option
if (foo() != 0) {
std::cout << "Exited on line " << __LINE__ << std::endl;
exit(1);
}
if (bar(1, 2, 3) != 0) {
std::cout << "Exited on line " << __LINE__ << std::endl;
exit(1);
}
// second option
EXIT_ON_FAILURE(foo());
EXIT_ON_FAILURE(bar(1, 2, 3));
// third option
exitOnFailure(foo(), __LINE__);
exitOnFailure(bar(1, 2, 3), __LINE__);
return 0;
}
我更喜欢这里的第二个选项,因为它简短而紧凑,大写锁定文本比驼峰的情况更清晰,更容易阅读。
这种方法有什么问题,特别是在C ++中,还是只是糟糕(但可以接受)的风格?
答案 0 :(得分:3)
宏是C / C ++的一个非常强大的功能,就像所有C功能一样 默认情况下指向你的脚。考虑以下用途 你的宏:
if (doSomething())
EXIT_ON_FAILURE(s) /* <-- MISSING SEMICOLON! OH NOES!!! */
else
doSomethingElse();
else
是否属于声明中的if
或if
通过展开EXIT_ON_FAILURE
创建?无论哪种方式,行为
一个缺少分号是完全出乎意料的。如果是EXIT_ON_FAILURE()
是一个函数,你会得到一个编译器错误。在这种情况下,你会得到
编译但做错事的代码。
这就是宏的问题。它们看起来像功能或 变量但不是。一个写得很糟糕的宏是礼物 继续给予。每次使用宏都是一个潜在的微妙的bug 对宏的每次更改都有可能将逻辑错误引入代码中 那你没碰过。
一般情况下,除非绝对必要,否则应避免使用宏。
如果您需要定义常量,请使用const
变量或enum
。
一个好的编译器(你可以免费获得)将把它们变成
生成的可执行文件中的文字就像#define'd常量一样
但它也会按照您的期望和意愿处理类型转换
显示在调试器的符号表中。
如果您需要类似内联函数的内容,请使用内联函数。 C ++和C99都提供了它们和大多数体面的编译器(包括 免费的)已经做了很长时间的延期。
函数强制它们的参数被评估,与宏不同,所以
inline int DOUBLE(int a) {return a+a;}
只会评估a
一次
#define DOUBLE(a) (a + a)
将评估a
两次。这意味着
x = DOUBLE(someVeryLongFunction());
如果DOUBLE是一个宏,那么将花费两倍的时间,而不是它是一个函数。
另外,我(故意)忘了将宏参数括起来,所以 这样:
DOUBLE(a << b)
将给出一个完全令人惊讶的结果。你需要记住 将DOUBLE宏写为
#define DOUBLE(a) ((a) + (a))
换句话说,您需要完美地编写一个宏来最小化 在脚下射击自己的机会。如果你弄错了, 你会支付多年的费用。
所有这一切,是的,是宏将会出现的情况
代码更具可读性。它们很少而且很远,但它们
存在。其中之一是您的代码重新发明的assert
宏。
复杂系统使用自己的自定义很常见
assert
- 像宏一样绑定到本地调试方案,以及那些
几乎总是用宏来实现,以获得__FILE__
,
__LINE__
和条件的文本。
但即便如此,这就是典型assert
的实施方式:
#ifdef NDEBUG
# define assert(cond)
#else
# define assert(cond) __assert(cond, __FILE__, __LINE__, #cond)
#endif
换句话说,类似函数的宏扩展为函数调用。
这样,当您致电assert
时,扩展仍然非常接近
它看起来像什么,论证扩展发生的方式
你期待它。
还有一些其他用途。基本上,任何时候你需要 将信息从构建过程本身传递给程序,它会 可能需要通过宏系统。即便如此,你呢 应该最小化触及宏的代码量以及如何 宏做的很多。
最后一件事。如果您想要使用宏,那么 代码会更快,请注意这是魔鬼说话。在 在过去的日子里,可能会出现转换小的情况 功能到宏中带来了显着的性能提升。这些 天虽然:
大多数编译器都支持内联函数。有些人甚至这样做 自动转为静态功能。
现代计算机速度如此之快,以至于你几乎肯定不会注意到 甚至调用一个微不足道的函数的开销。
只有当您的编译器不执行内联函数和时,您才能做到这一点 用更好的和代替你 证明该函数调用 开销是一个瓶颈,你可以证明编写一些宏是合理的。
可能。
答案 1 :(得分:0)
确保宏可以简化功能,使其更易于阅读。但是你应该考虑使用内联函数。
在您的示例中,EXIT_ON_FAILURE可以是内联函数。宏不仅使编译器错误不准确(它可能导致一些错误显示在错误的地方),使用宏时要注意一些事情,特别是变量,请考虑这个例子:
#define MY_MACRO(s) if (s * 2 >= 20) foo()
// later on your code:
MY_MACRO(5 + 5);
虽然我可以期待foo()给我打电话,但它不会,因为它不会扩展到if (10 * 2 >= 20) foo()
,它会扩展为if (5 + 5 * 2 >= 20) foo()
。所以你需要记住在定义宏时总是在变量周围使用()。
宏也使程序更难调试。
答案 2 :(得分:0)
有时候你需要的是宏,但你应该尽量减少它们的数量。在您的示例中,已经存在一个名为“assert”的宏,您可以使用该宏而不是创建新宏。
C ++有许多功能允许你在没有需要C语言宏的宏的情况下做事,所以宏在C ++代码中应该比在C代码中更少见。