条款02:尽量用const,enum,inline替换#define

条款02:尽量用const,enum,inline替换#define

Prefer consts, enums, and inlines to #defines

“宁可用编译器替换预处理器”

对于定义的一个宏:

#define ASPECT_RATIO 1.653

对于这样的一个记号ASPECT_RATIO,可能从未被编译器看到,也可能在编译器开始处理源码之前它就被预处理器移走了。因此,记号ASPECT_RATIO 可能没有进入记号表(symbol table)。

在计算机科学中,符号表(记号表)是一种用于语言翻译器(例如编译器和解释器)中的数据结构。在符号表中,程序源代码中的每个标识符都和它的声明或使用信息绑定在一起,比如其数据类型、作用域以及内存地址。

因此,当在程序中运用此常量但获得一个编译错误时,错误信息可能会提到1.653,但不是ASPECT_RATIO。若是这个ASPECT_RATIO被定义在不是自己所写的头文件中,更是会对这样的来源毫无概念。
原因就是:你说使用的名称可能并未进入记号表中(symbol table)

解决办法:

用一个常量替换宏(#define)

//全部大写用于宏
//此时采用普通变量的书写形式
const double AspectRatio = 1.653;

作为一个语言常量,AspectRatio 肯定会被编译器看到,也就一定会进入记号表中。

当我们以常量来替换#define时,有两种特殊的形式

1,定义常量指针(constant pointers)

因为常量定义式通常被放到头文件中(以便被不同的源码含入),因此,有必要将指针(而不只是指针所指物)声明为const
例如,想要在头文件中定义一个常量的(不变的)char*-based字符串,必须要写const两次

const char* const autherName = "Scott Meyers";

当然,string对象通常比char*-based更为合适:

const std::string autherName("Scott Meyers");

2,class专属常量

为了将常量的作用域限制在class内部,必须要让它成为class的一个成员(member);而且,为了确保次常量至多只有一份实体,必须让它成为一个static成员

class GamePlayer {
private:
    static const int NumTurns = 5;//常量声明式
    int scores[NumTurns];//使用该常量
    ...
}

需要注意的是,上述NumTurns是声明式而并非定义式
通常来说,C++要求你对于以所要使用的任何东西都必须提供一个定义式,但是如果它是个class专属常量又是static并且为整数类型(integral type,例如ints,chars,bools),则需要特殊处理。只要不去取它们的地址,你可以声明并使用它们,而无需提供定义式
但是,如果你需要去取某个class专属常量的地址,或纵使你不取地址但编译器(错误地)坚持要看到一个定义式,你就必须另外提供定义式:

const int GamePlayer::NumTurns;//NumTurns的定义

上述的表达式需要放到一个实现文件中而非头文件。这是因为,class常量已经在声明时就获得了初值(例如上面所声明的初值为5),因此,定义时不可以再设置初值

另外,我们无法利用#define创建一个class专属常量,因为#define并不重视作用域。一旦宏被定义,它就在其后的编译过程中已知有效(除非在某处定义了#undef)
这意味着,#define不仅不能够用来定义class专属常量,也不能提供任何封装性——即没有所谓这private #define这样的东西。
然而,const成员变量是可以被封装的

同时,我们也可以将初值放到定义式中:

class CostEstimate {
private:    
    static const double FudgeFactor;//static class常量声明
    ...                             //位于头文件内
};
const double CostEstimate::FudgeFactor = 1.35l//static class常量定义
                                              //位于实现文件内

然而,如果编译器(错误地)不允许“static整数型class常量”完成“in class初值设定”,可以改用“the enum hack”的补偿做法。即:

一个属于枚举类型(enmuerated type)的数值可权充ints被使用。

因此,上述的GamePlayer可以以如下的形式定义:

class GamePlayer {
private:    
    enum { NumTurns = 5 };//“the enum hack”——令NumTurns成为5的一个记号名称    
    int scores[NumTurns];
    ...
};

对于enum hack:

  • enum hack的行为在某些方面更像#define而不是const。例如,取一个const地址是合法的,但是取一个enum的地址就不合法了,而取一个#define的地址通常也不合法
    如果你不想让别人活得一个指针或引用,以指向你的某个整型常量,enum可以实现这个约束。
  • enum hack体现了实用主义。实际上,“enum hack”“template metaprogramming(模板元编程)”的基础技术。

另一个#define的误用情况:以它实现宏(macros)

对于宏,看起来像是函数,但是不会招致函数调用(function call)带来的额外开销。
下面这个宏带有宏参数,调用函数f:

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

上述形式有太多的缺点!
首先,无论何时,宏中的所有实参都必须要加上小括号。但是,即使加上了小括号,还是会有问题!

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)//因为我们不知道T的类型,
{                                              //因此使用pass by reference-to-const
    f(a > b ? a : b);
}

最后,有了const,enum,inline,我们对预处理器(尤其是#define)的需求大大降低了,但并没有完全消除。#include仍然是必需品,而#ifdef / #ifndef也继续在控制编译过程中起到重要的作用。

1、对于单纯常量,最好以const对象或enum替换#define。

2、对于形似函数的宏(macros),最好改用inline函数替换#define。

猜你喜欢

转载自blog.csdn.net/lym940928/article/details/80668247