C/C++ 避免使用宏
概述:
宏是C和C++语言的抽象设施中最生硬的工具,它是披着函数外衣的饥饿的狼,很难驯服,它会我行我素地游走于各处。要避免使用宏。
讨论:
在C++中,几乎从不需要使用宏。
可以用const或者enum定义易于理解的常量,用inline避免函数调用的开销,用template指定函数系列和类型系列,用namespace避免名称冲突。
C++的宏的主要问题在于,它们表面上看起来很好,而实际上做的却是另一回事。宏会忽略作用域,忽略类型系统,忽略所有其他的语言特性和规则,而且会劫持它为文件其余部分所定义(#define)的符号。宏调用看上去很像符号或者函数调用,但实际上并非如此。宏不太“卫生”,也就是说,它会根据自己被使用时所处的环境引人注目而且令人惊奇地展开为各种东西。宏需要进行文本替换,因此编写远距离也正确的宏接近于一种魔法,而精通这种魔法既无意义又无趣味。
不少人认为与模板相关的错误都是最难以解读的,他们可能还没有看到误写和误用的宏所引起的那些错误。模板是C++类型系统的一部分,因此编译器可以更好地对它们进行处理,而宏天生是与语言本身割裂开来的,因此很难处理。更糟的是,与模板不同,宏可能展开为在偶然情况下能够编译的“传输线噪音”。最后,宏中的错误可能只有在宏展开之后才能被报告出来,而不是在定义时。
即使在极少的情况下,有正当理由编写宏,也决不要考虑编写一个以常见词或者缩略语为名字的宏。尽可能快的取消宏的定义(#undef)。
示例:
1、定义一个宏#define min(n, m) ((n) < (m) ? (n) : (m))
定义两个变量a和b,min(++a, b) 传入之后是这样 ((++a) < (b) ? (++a) : (b)) 如果++a小于b的话,a就自加了两次,很明显不符合宏使用的初衷。
2、将模板实例化转给宏,宏仅能理解C语言的小括号和方括号,并将其进行匹配。然而,C++又定义了一个新的括号结构,即模板中使用的尖括号<和>。宏无法正确的匹配它们,这意味着在下面的宏调用中:
MACRO(Foo<int,double>)
宏会认为传给自己的是两个参数,即Foo<int和double>,而事实上该结构是一个C++实体。
例外情况:
宏仍然是几个重要任务的唯一解决方案,比如#include保护符,条件编译中的#ifdef和#ifndef,以及assert的实现。
在条件编译中,要避免在代码中到处杂乱地插入#ifdef。相反,应该对代码进行组织,利用宏在驱动一个公共接口的多个实现,然后始终使用该接口。
如果不想到处复制粘贴代码段,那么可以使用宏,但要非常小心。