我一直问这个问题,但我从来没有得到一个很好的答案;我认为几乎所有程序员在写第一个“Hello World”之前都遇到过“永远不应该使用宏”、“宏是邪恶的”之类的短语,我的问题是:为什么?这么多年后,有了新的 C++11,还有真正的替代品吗?
简单的部分是关于 #pragma
类的宏,它们是特定于平台和编译器的,并且大多数时候它们有严重的缺陷,例如 #pragma once
这在至少两种重要情况下容易出错: 在不同的路径和一些网络设置和文件系统中具有相同的名称。
但总的来说,宏及其用法的替代品呢?
原文由 user1849534 发布,翻译遵循 CC BY-SA 4.0 许可协议
宏就像任何其他工具一样 - 用于谋杀的锤子并不邪恶,因为它是锤子。人们以这种方式使用它的方式是邪恶的。如果你想敲钉子,锤子是一个完美的工具。
宏有几个方面使它们“不好”(我稍后会扩展每个方面,并提出替代方案):
所以让我们在这里稍微扩展一下:
1) 无法调试宏。 当您有一个转换为数字或字符串的宏时,源代码将具有宏名称,并且许多调试器无法“看到”宏转换为什么。所以你实际上并不知道发生了什么。
替换:使用
enum
或const T
对于“类似函数”的宏,因为调试器在“每个源代码行”级别上工作,所以无论是一条语句还是一百条语句,您的宏都将像一条语句一样运行。很难弄清楚发生了什么。
替换:使用函数 - 如果需要“快速”,则使用内联(但要注意内联过多不是一件好事)
2) 宏扩展会产生奇怪的副作用。
著名的是
#define SQUARE(x) ((x) * (x))
和使用x2 = SQUARE(x++)
。这导致x2 = (x++) * (x++);
,即使它是有效的代码 [1],也几乎肯定不是程序员想要的。如果它是一个函数,那么做 x++ 就可以了,x 只会增加一次。另一个例子是宏中的“if else”,假设我们有这个:
接着
它实际上变成了完全错误的事情……
替换:真正的功能。
3) 宏没有命名空间
如果我们有一个宏:
我们在 C++ 中有一些使用 begin 的代码:
现在,你认为你得到了什么错误信息,你在哪里寻找错误[假设你完全忘记了——或者甚至不知道——存在于其他人编写的某个头文件中的 begin 宏? [如果你在 include 之前包含那个宏,那就更有趣了——你会沉浸在奇怪的错误中,当你查看代码本身时,这完全没有意义。
替换:好吧,与其说是替换,不如说是“规则”——只对宏使用大写名称,而从不将所有大写名称用于其他事物。
4)宏有你没有意识到的效果
采取这个功能:
现在,不看宏,你会认为 begin 是一个函数,它不应该影响 x。
这类事情,我见过更复杂的例子,真的会搞砸你的一天!
替换:要么不使用宏来设置 x,要么将 x 作为参数传入。
有时使用宏绝对是有益的。一个例子是用宏包装一个函数来传递文件/行信息:
现在我们可以使用
my_debug_malloc
作为代码中的常规malloc,但是它有额外的参数,所以当我们扫描“哪些内存元素没有被释放”时,我们可以打印在哪里进行分配,以便程序员可以追踪泄漏。[1] “在一个序列点”多次更新一个变量是未定义的行为。序列点与语句并不完全相同,但对于大多数意图和目的而言,我们应该将其视为。这样做
x++ * x++
将更新x
两次,这是未定义的,可能会导致不同系统上的不同值,以及x
中的不同结果值。