模板与预处理器宏

有时可能会出现一个问题:是否可以使用宏替换来生成代码?这实际上是可以的。例如,Max 函数集可以很容易地表示为宏:

#define MAX(V1,V2) ((V1) > (V2) ? (V1) : (V2))

然而,宏的功能比较有限(仅限于文本替换),因此它们仅用于简单情况(例如上述情况)。

在比较宏和模板时,应注意以下区别。

宏在编译开始之前,会被预处理器在源文本中“扩展”并替换。同时,没有关于参数类型以及宏内容替换上下文的信息。特别是,宏 MAX 无法检查参数 V1 和 V2 的类型是否相同,以及是否已为它们定义比较运算符 '>'。此外,如果在程序文本中遇到名为 MAX 的变量,预处理器会尝试用 MAX 宏的“调用”来替换它,但会因为缺少参数而报错。更糟糕的是,这些替换会忽略 MAX 标记所在的命名空间或类―基本上,任何命名空间或类都可以。

与宏不同,模板由编译器根据特定的参数类型及其使用位置进行处理,因此它们可以对模板中的所有表达式进行类型兼容性(和普遍适用性)检查以及上下文绑定。例如,我们可以在具体类中定义方法模板。

如果需要,可用不同方式定义同名模板,以用于不同类型,而给定名称的宏始终会被相同的“实现”替换。例如,对于像 MAX 这样的函数,我们可以为字符串定义不区分大小写的比较。

宏自身问题导致的编译错误很难诊断,特别是当宏包含多行代码时,因为高亮显示的错误行是宏的“调用”位置,而不是预处理器展开后的文本,后者才是传递给编译器的内容。

同时,模板是源代码中以现成形式存在的元素,因为它们会进入编译器,因此其中的任何错误都会在行中具有特定的行号和位置。

宏可能会产生副作用,这一点我们已在 伪函数形式的 #define 一节讨论过:如果 MAX 宏自变量是带有增量/减量的表达式,则它们将被执行两次。

然而,宏也有一些优势。宏能够生成任何文本,而不仅仅是正确的语言结构。例如,只需要几个宏,您便可以模拟用于字符串的 switch 指令(尽管不推荐这种方法)。

具体而言,在标准库中,宏用于组织图表上事件的处理(参见 MQL5/Include/Controls/Defines.mqh:EVENT_MAP_BEGIN、EVENT_MAP_END、ON_EVENT 等)。它不适用于模板,而且使用宏来构建事件映射显然不是唯一的选择,也不是最便捷的调试方案。在宏中,逐步(逐行)调试代码执行非常困难。相反,模板完全支持调试。