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

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

  该条款或许改为"宁可以编译器替换处理器"比较好, 因为或许#define 不被视为语言的一部分。 那正是它的问题所在。 当你做出这样的事情:
#define ASPECT_RATIO 1.653
  记号名称ASPECT_RATIO也许从未被编译器看见;也许在编译器开始处理源码之前它就被预处理器移走了。 于是记号名称ASPECT_RATIO有可能没有进入符号表(symbol table)内。 于是当你运用此变量但获得一个编译错误信息时, 可能会带来困惑, 因为这个错误信息也许会提到1.653而不是ASPECT_RATIO。如果ASPECT_RATIO被定义在一个非你所写的头文件内, 你肯定对1.653以及它来自何处毫无概念, 于是你将因为追踪它而浪费时间。 这个问题也可能出现在符号式调试器中,原因相同: 你所适用的名称可能并未进入符号表(symbol table)。
  解决方法是用一个常量替换上述的宏(#define):

const double ASPECT_RATIO = 1.653

  作为一个语言常量, ASPECT_RATIO 肯定会被编译器看到, 当然就会进入符号表内。 此外对浮点常量(floating point constant) 而言, 使用常量可能比适用#define 导致较小量的码, 因为预处理器“盲目地将宏名称ASPECT_RATIO 替换为1.653” 可能导致目标码(object code)出现多份1.653, 若改用常量ASPECT_RATIO绝不会出现相同情况。
  当我们以常量替换#defines, 有两种特殊情况比较重要。
  第一,定义常量指针(constant pointers)。 由于常量定义式通常被放在头文件内(以便被不同的源码包含), 因此有必要将指针(而不只是指针所指之物)声明为const。 例如若要在头文件内定义一个常量的 char &-based字符串, 你必须写const 两次:

const char* const AUTHOE_NAME = "Scott Meyers";

   关于const 的意义和使用(特别是当它与指针结合时), 条款3有完整的讨论。这里值得先提醒你的是, string对象通常比其前辈 char *-based 合适, 所以上述的AUTHOE_NAME 往往定义成下面更好些:

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

 第二个值得注意的是class 专属常量。 为了将常量的作用域(scope)限制于class内, 你必须让它成为class的一个成员(number); 而为确保此常量至多只有一份实体, 你必须让它成为一个static成员:

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

  然而你所看到的是NUM_TURNS的声明式而非定义式。 通常C++要求你对你所使用的任何东西提供一个定义式, 但如果它是个class专属常量又是static且为整数类型(例: int, char, bool), 但如果你取某个class专属常量的地址, 或即使你不取其地址而你的编译器却坚持要看到一个定义式, 你就必须另外提供定义式如下:

const int GamePlayer::NUM_TURNS; // NUM_TURNS的定义; 下面将会表述为什么没有给予数值

   请把这个式子放进一个实现文件而非头文件。 由于 class 常量已在声明时获得初值(例如先前声明NUM_TURNS时为它设初值5), 因此定义时不可以再设初值。
   请注意, 我们无法利用#define 创建一个class专属变量, 因为#defines并不重视作用域(scope)。 一旦宏被定义, 它就在其后面的编译过程中有效(除非在某处被#undef)。这意味#defines不仅不能够用来定义class 专属变量,也不能够提供任何封装性, 也就是说没有所谓private #define 这样的东西。而当然const成员变量是可以被封装的, NUM_TURNS就是。
  旧式编译器也许不支持上述语法, 它们不允许static成员在其声明式上获得初值。 此外所谓的"in-class初值设定"也只允许对整数常量进行。 如果你的编译器不支持上述语法, 你可以将初值放在定义式:

class CostEstimate{
private:
	static const double FudgeFactor; // static class 常量声明, 位于头文件内
};

const double CostEstimate::FudgeFactor = 1.35 // static class 常量声明定义, 位于实现文件内

   这几乎是你在任何时候唯一需要做的事。 唯一例外是当你在class编译期间需要一个class常量值, 例如在上述的GamePlayer::scores的数组声明中(是的, 编译器坚持必须在编译期间知道数组的大小)。 这时候万一你的编译器"the enum hack"补偿做法。 其理论基础是:“一个属于枚举类型(enumerated type)的数值可权充ints被使用”, 于是GamePlayer可定义如下:

class GamePlayer {
private:
	enum{ NUM_TURNS = 5 }; // "the enum hack" 令 NUM_TURNS
	int scores[NUM_TURNS];
}

   基于数个理由enum hack 值得我们认识。
  第一, enum hack 的行为某方面说比较像#define而不像const, 有时候这正是你想要的。 例如取一个const的地址是合法的, 但取一个enum的地址就不合法, 而取一个#define 的地址通常也不合法。如果你不想让别人获得一个pointer或reference指向你的某个整数常量,enum可以帮助你实现这个约束。 此外虽然优秀的编译器不会为"整数型const对象"设定另外的存储空间(除非你创建一个pointer或reference指向该对象), 不够优秀的编译器却可能如此, 而这可能是你不想要的。 Enums和#defines一样绝对不会导致非必要的内存分配。
 第二, 认识enum hack纯粹是为了实用主义。 许多代码用了它, 所以看到它时你必须认识它。 事实上"enum hack"是template metaprogramming (模板元编程)的基础技术。
   回到预处理器。另一个常见的#define 误用情况是以它实现宏(macros)。宏看起来像函数, 但不会招致函数调用带来的额外开销。 下面这个宏带着宏实参, 调用函数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 的递增次数竟然取决于"它被拿来和谁比较"!
   幸运的是你不需要对这种无聊事情提供温床。 你可以获得带来的效率以及一般函数的所有可预料行为和类型安全性(type safety)——只要你写出template inline函数(见条款30):

template<typename T> // 由于不知道T是什么, 所以采用pass-by reference-to-const 见条款30
inline void callWithMax(const T& a, const T& b)
{
	f(a >b ? a : b);
}

   这个template 产出一整群函数, 每个函数都要接受两个同型对象, 并以其中最大者调用f。这里不需要在函数本体中为函数加上括号, 也不需要操心参数被求值多次…等等。 此外由于callWithMax是个真正的函数, 它遵守作用域(scope)和访问规则。 例如你绝对可以写出一个"class内的private inline函数"。 一般而言宏无法完成此事。
   有了const,enum和inline, 我们对预处理器(特别是#define)的需求降低了, 但并非完全消除。 #include仍然是必需品, 而#ifdef/ #ifndef也继续扮演控制编译的重要角色。 目前还不到预处理器全面引退的时候, 但你应该明确地给予他更长更频繁地假期。
总结
1. 对于单纯常量, 最好以const对象或enum替换#define。
2. 对于相似函数的宏(macro), 最好改用inline函数替换#define。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值