宏是否使代码更具可读性
Do macros make the code more readable?
我有一个关于宏及其可读性的辩论。我认为在某些情况下,使用宏可以使代码更短、更易于理解且阅读起来更不累。
例如:
#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++中,还是只是糟糕(但可以接受)的风格?
是 C/C++ 的一个非常强大的功能,就像所有 C 功能一样默认指向您的脚。 考虑以下使用您的宏:
if (doSomething())
EXIT_ON_FAILURE(s) /* <-- MISSING SEMICOLON! OH NOES!!! */
else
doSomethingElse();
else
属于语句中的if
还是if
通过扩展EXIT_ON_FAILURE
创建? 无论哪种方式,行为缺少一个分号是完全出乎意料的。 如果EXIT_ON_FAILURE()是一个函数,你会得到一个编译器错误。 在这种情况下,你会得到编译但执行错误操作的代码。
这就是宏的问题。 它们看起来像函数或变量,但事实并非如此。 写得不好的宏是礼物不断给予。 宏的每次使用都是一个潜在的微妙错误,并且对宏的每次更改都有可能在代码中引入逻辑错误你没有碰。
通常,除非绝对必要,否则应避免使用宏。
如果需要定义常量,请使用 const
变量或 enum
。一个好的编译器(您可以免费获得)会将它们变成生成的可执行文件中的文字,就像一个 #define 常量一样但它也会按照您期望的方式处理类型转换,并且会显示在调试器的符号表中。
的东西,请使用内联函数。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
时,扩展仍然非常接近到它的样子和参数扩展的方式发生你会期望它。
还有一些其他用途。 基本上,任何时候你需要将信息从构建过程本身传递给程序,它将可能需要通过宏观系统。 即便如此,你应尽量减少接触宏的代码量以及如何宏做了很多。
最后一件事。 如果您因为认为而想使用宏代码会更快,请注意这是魔鬼在说话。 在过去,可能有一些情况,转换小宏中的函数带来了明显的性能改进。 这些虽然天:
大多数编译器都支持内联函数。 有些人甚至这样做自动到静态函数。
现代计算机的速度如此之快,以至于您几乎肯定不会注意到调用甚至微不足道的函数的开销。
仅当你的编译器不执行内联函数并且你不能只是用更好的替换它,你已经证明了函数调用开销是一个瓶颈,您是否可以证明编写一些宏是合理的。
或。
当然,宏可以简化函数,使其更易于阅读。但您应该考虑改用内联函数。
在您的示例中,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()
。因此,您需要记住在定义宏时始终在变量周围使用 ()。
宏还使程序更难调试。
有时宏是您需要的,但您应该尽量减少它们的数量。 在您的示例中,已经有一个名为"assert"的宏,您可以使用它而不是创建一个新宏。
C++ 具有许多功能,允许您在没有宏的情况下执行操作,而宏在 C 中需要宏,因此宏在C++代码中应该比在 C 代码中更不常见。
- C++我的数学有什么问题,为什么我的代码不能正确循环
- 代码在main()中运行,但在函数中出现错误
- 在VS代码中交叉编译Windows与Linux上的MinGW的SDL程序
- 编译包含字符串的代码时遇到问题
- 我在c++代码中生成了一个运行时#3异常
- 如何在linux终端中同时编译和运行c++代码
- 为cl.exe(Visual Studio代码)指定命令行C++版本
- 在Linux for Windows上编译C++代码时出错
- 我的字符计数代码计算错误.为什么
- 孤立代码块在结构中引发异常
- 在编译C++代码(具有dlib和opencv)到WASM时面临问题
- 为什么我的C#代码在调用回C++COM直到Task时会暂停.等待/线程.加入
- 处理小于cpu数据总线的数据类型.(c++转换为机器代码)
- 此代码是否违反一个定义规则
- 是否值得降低我的代码的可读性,以便在出现内存不足错误时提供异常安全性?
- 发布代码的 gdb 堆栈跟踪可读性如何影响 x64?
- 宏是否使代码更具可读性
- 定义类似关键字的宏只是为了提高代码的可读性
- 现代c++中的内联方法和代码可读性
- 代码可读性与c++11 lambdas