您的位置:首页 > 编程语言 > C语言/C++

C++编程规范 编程风格 学习 (2) -- 避免使用宏

2012-12-31 01:17 323 查看

避免使用宏

实不相瞒:宏是C和C++语言的抽象设施中最生硬的工具,它是披着函数外衣的饥饿的狼,很难驯服,它会我行我素地游走于各处。要避免使用宏。

由于几方面的原因,宏已经成为讨厌、恶心、杂乱的混合体,其中最主要的原因在于它们被吹捧为一种文本替换设施,其效果在预处理阶段就产生了,而此时C++的语法和语义规则还都没起到作用。

在C++中几乎从不需要宏。可以用const或者enum定义易于理解的常量,用inline避免函数调用的开销,用template制定函数系列和类型系列,用namespace避免名字冲突。

关于宏的第一规则就是:不要使用它,除非不得不用,几乎每个宏都说明程序设计语言、程序或者程序存在缺陷。

宏和模板不同,template是C++类型的一部分,宏和语言本身是割裂开来的,template可以借助编译器,而宏不行,template的错误可在编译的时候就发现,编译器也能更好地处理它,而宏里的错误只有在宏展开的时候才能被发现,而不是定义宏的时候。

但是,
宏还是必不可少的,比如#include保护,条件编译中的#ifdef和#if defined,以及assert的实现。
在条件编译中,要避免在代码中杂乱的插入#ifdef,相反,应该对该代码进行组织,利用宏来驱动一个公共接口的多个实现,然后始终使用该接口。
如果不想到处复制和粘贴代码段,可以使用宏,但是要非常小心。

关于预处理:

预处理可能是C++翻译过程中最危险的阶段,预处理器注重构成C++源码的”word“,但是它会忽略C++的细节,包括语法和语义,实际上,预处理器并不知道它的能力有多大,其实它的破坏力是巨大的。
使用预处理器最好是,需要强大的力量,但是不需要过多的了解C++,并避免需要技巧的工作。

#define 字面值
C++中使用#define定义字面值会导致错误或潜在的问题,所以C++程序员并不使用#define来定义字面值,考虑C中的#define的标准用法。

#define MAX 1<<16
使用预处理器符号的基本问题在于,预处理器在C++编译器检查这些符号之前,就展开了这些符号,预处理器对C++作用域或者类型规则一无所知。
void f( int );
void f( long );
//...
f(MAX);     //这里改用哪个f呢?

这里编译器进行重载分析时,MAX这个符号的值就是整数1<<16,而1<<16可能是int也可能是long,这就要取决于编译目标的平台,不同平台下编译上述代码,会调用不同的函数。
#define 指令不会关心作用域。现在多处C++工具都封装在namespace中,这有不少优点,而不仅仅是降低了不同工具互相干扰的可能性,而#define的作用域不止限于某个namespace内。
namespace A
{
#define MAX 1<<16
//....
}

namespace B
{
const int max = 512;
//....
}

int arr[MAX];
如果在此忘记了导入名称max,而误写为MAX。预处理器会把1<<16替换为MAX, 所以可以编译代码。这个预处理器就跨越了C++的作用域。

如果这么用问题就会更大
int arr2[MAX*2];
这个数组大小就会超出自己当初的预料,它实际上是arr2[1<<16*2];
这就是不正确地构造#define而引起的。如果使用初始化好的常量就不会出这种语义问题。
const int max = 1<<16;
int arr3[max*3];
这才是我想要的。

#define 伪函数
在C中,#define 通常用于定义伪函数,这种情况就要更多地考虑避免函数调用的代价,而不是安全性。

#define MULTIPLE( a, b ) (a * b)

这么写就有问题,如果是MULTIPLE(2+3,4)
其实你想要(2+3)*4,但是实际是2+3*4,这差了很远,不如用inline来实现岂不更好,
int inline multiple(int a, int b){return a*b;};
这绝不会产生歧义。
当然你给带上括号,给好约束就好了#define MULTIPLE( a, b ) ((a) * (b)),是不是觉得OK了?其实不然,看下面
#define SQUARE( a ) ((a) * (a)) 这个看似没啥问题。

但是还会有可能有问题
#define SQUARE( a ) ((a) * (a))
int nValue = 10;
int nSquare = SQUARE(nValue++); // nSquare=110, nValue=12
想想为啥是这个结果?
这就是宏在展开时,对其参数的多次取值替换所带来的副作用。为了避免这种副作用,禁止在使用宏的时候,参数有变化。
这就是伪函数的恐怖之处。

另外,有很多宏是很有用的,比如说断言ASSERT,但是它也是伪函数,是伪函数就有它致命的地方,所以需要正确的使用它才能达到目的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: