《Effective C++ 》学习笔记——条款02



****************************  一、 Accustoming Yourself to C++ **************************** 


条款02: Prefer consts,enums,and inlines to #defines


上一个条款,让我正确认识C++,并非是一个一体语言,而是一个联邦,

而这一个条款,是在纠正我们的一些行为习惯。


尽可能的用const、enum、inline 而非 #define

这个就要扯到 编译器预处理器 的方面了,

#define 所设置的内容会在预处理器的时候被移走,因此编译器会看不到它。

这时候,会出现这种问题:

#define ASPECT_RATIO 1.653

而当 ASPECT_RATIO 出问题时,不会报错这个常量,而是报错  1.653 这个数字,

——Why? 

所使用的名称可能并未进入到symbol table 内。

在编译器中,会将这些变量常量名称  归纳到 symbol table(记号表)内。

但是,这个常量被预处理器移走了,因此,没有进入 symbol table,所以如果编译出错,错误信息会提到1.653 而非ASPECT_RATIO

这就对我们追踪,查错造成很多不必要的时间上的浪费。

如果用const代替:

const double AspectRatio = 1.653;  // 因为非#define 名称也要有相应规范的变化

这时,这个常量名就会进入 symbol table,因此如果编译出错,错误信息回是 AspectRatio


不仅仅如此,对于浮点常量而言,使用常量const可能比#define导致较小量的码,因为预处理器“盲目”将宏名称 ASPECT_RATIO 替换成 1.653 可能导致 object code ( 目标码 )出现多份 1.653 ,若使用const 则会杜绝相同情况。


还有,对于const 替换 #define 还有两种特殊情况

① 定义 constant pointers (常量指针)

在头文件定义一个常量指针需要用两个const,我的理解是,一个定义指针不能指向别的地址,一个是定义指针内容不可变。这个具体还会在下一个条款具体讨论。 

再有一点就是对于定义一个字符串,

用 const std::string authorName("Scott Meyers");

优于 const char* const authorName = "Scott Meyers";


② 关于class的专属常量

这是有关于 scope(域) 的一个概念,如果想将一个常量作用域限制于一个类内,你就必须让这个常量成为类内的一个 member(成员),这时为了让这个类内常量至多只有一个实体,还必须让它成一个 static(静态) 成员。

当然,这个用#define做不到,因为#define 并不重视 作用域,一旦被定义,它在其后的编译过程都有效,除非在某处#undef,所以 const 是可被封装的。

还有一点,在类内的const是声明式而非定义式,如果编译器非要求你出示一个定义式给它,可以这样实现:

class GamePlayer {

private:

static const int NumTurns = 5;

int scores[NumTurns];

......

};


const int GamePlayer::NumTurns; // 常量已在声明时获得初值,就无须再设初值

这就是 "in-class" 初值设定,

这个设定也有限制,要求class内的常量static而且integral type(整数类型){例如:ints,chars,bools}

但此处限制是对于整数类型的,如果是其他类型,可以类内声明不设初值,直接在类外定义时再设初值




OK,关于const 说完,接下来就是 enum


对于上述情况,如果类在编译期间需要一个class常量值,而恰巧编译器不支持或不允许 static 整数型 class 常量 完成 in-class 初值设定,那就可以使用 the enum hack 补偿方法

它的理论基础是—— 一个属于 enumerated type(枚举类型)的数值可权充int被使用

例:

class GamePlayer{
private:
    enum { NumTurns = 5 };
    int scores[NumTurns];
    ......
};

当然,enum作用不仅仅这样,有很多理由让我们,必须认识它

enum hack 行为比较像 #define 而非 const ,取const地址是合法的,而取enum和#define地址通常是不合法的,enum 和 #define 一样绝不会导致非必要内存的浪费

② enum hack  是 template metaprogramming (模板元编程)的基础技术




then 是 inline 方面


有一个常见的#define误用情况是用它来实现macros(宏)

宏看起来像函数,但不会招致 function call (函数调用)带来额外开销。

下面这个例子,就是宏夹着宏实参,调用函数f:

// 以a和b的较大值调用f
#define CALL_WITH_MAX(a,b) f( (a) > (b) ? (a) : (b) )

说实话,宏长这样真的会让人痛苦啊。。。

这还只是简单的取较大的调用f,如果复杂点的函数,那画面不敢想!

PS:要注意,无论什么时候写带实参的宏,要为宏中所有的实参带上小括号。

调用它的例子:

int a = 5 , b = 0 ;
CALL_WITH_MAX(++a,b);    // a被累加两次
CALL_WITH_MAX(++a,b+10);    // a被累加一次

这里,调用f之前,a的累加次数,竟然取决于“它被拿来和谁比较”!


这时,你就需要一个 template inline 函数 解决问题:

template<typename T>
inline void callWithMax( const T& a, const T& b){
    f( a > b ? a : b );
}

此外,由于这是一个真正的函数,它遵守scope(作用域)的概念和访问规则,因此,完全可以封装在类内,当然这是宏无法做到的。


结束语:

虽然 有了const、enum、inline以后,我们对预处理器,尤其是 #define 的需求降低了,但并非完全消除。#include 仍然是必需品,#ifdef/#ifndef也继续扮演控制编译的重要角色。目前还没有到预处理器隐退的时候,但是我们可以明确的给它更长的假期。



Please remember:

① 对于单纯常量,最好以const 对象或者enum 替换 #define

② 对于形似函数的宏,最好改用 inline函数 替换 #define



End....

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。