条款01:视C++ 为一个语言联邦
View C++ as a federation of languages.
今天的C++ 已经是一个同时支持过程形式(procedural)、面向对象形式(object-oriented)、函数形式(functional)、泛型形式(generic)、元编程形式(metaprogramming)的语言。将C++ 视为一个由相关语言组成的联邦而非单一语言。
(1)C:说到底C++ 仍是以C为基础。C语言的局限:没有模板(templates),没有异常(exceptions),没有重载(overloading)……
(2)Object-Oriented C++:类、封装、继承、多态、virtual函数……等等
(3)Template C++:泛型编程(自己最没经验的部分)
(4)STL。
次语言的切换时,高校编程守则的策略可能不同:
内置(C-like)类型:传值优于传引用;在STL中亦是如此,因为迭代器和函数对象都是在C指针之上塑造出来的。
Object-Oriented C++:由于用户自定义构造函数和析构函数的存在,pass-by-reference-to-const更好;Template C++亦是如此。
条款02:尽量以const,enum, inline替换 #define
Prefer consts,enums, and inlines to #defines.
宁可以编译器替换预处理器,因为或许 #define不被视为语言的一部分。
#define ASPECT_RATIO 1.653
宏通常用大写名称标记,记号名称ASPECT_RATIO也许从未被编译器看见;也许在编译器开始处理源码之前它就被预处理器移走了。于是记号名称ASPECT_RATIO有可能没进入记号表(symbol table)内。当运用此常量但获得编译错误时,错误信息可能会提到1.653而不是ASPECT_RATIO;也可能ASPECT_RATIO在别的头文件里,为追踪带来麻烦。
解决之道:
const double AspectRatio = 1.653;
作为一个语言常量,AspectRatio肯定会被编译器看到,当然就会进入记号表内。此外,使用常量可能比使用#define导致较小量的码,因为预处理器"盲目地将宏名称ASPECT_RATIO替换为1.653"可能导致目标码出现多份1.653,而用常量AspectRatio绝不会出现相同情况。
class专属常量:为了将常量的作用域限制于class内,你必须让它成为class的一个成员;而为确保此常量至多只有一份实体,你必须让它成为一个static成员:
class CostEstimate {
private:
static const double FudgeFactor;//staticclass常量声明
... //位于头文件内
};
const double //static class常量定义
CostEstimate::FudgeFactor = 1.35;//位于实现文件内
我们无法利用#define创建一个class专属常量,因为#defines并不重视作用域。一旦宏被定义,它就在其后的编译过程中有效(除非在某处被#undef)。这意味#defines不仅不能够用来定义class专属常量,也不能够提供任何封装性,也就是说没有所谓private#define(概念上)这样的东西;而const成员变量是可以被封装的。
当class编译期间需要一个class常量值,如类的数组声明式中,编译器坚持必须在编译期间知道数组的大小,可采用“theenum hack”补偿做法。其理论基础是:"一个属于枚举类型(enumerated type)的数值可权充int被使用"。
class GamePlayer {
private:
enum { NumTurns = 5 }; //"the enum hack" - 令NumTurns成为5的一个记号名称.
int scores[NumTurns]; //这就没问题了.
...
};
enum hack的行为某方面说比较像 #define而不像const,例如取一个const的地址是合法的,但取一个enum的地址或#define的地址通常都不合法。如果你不想让别人获得一个pointer或reference指向你的某个整数常量,enum可以帮助你实现这个约束。enums和#defines一样绝不会导致非必要的内存分配。
另一个常见的#define误用情况是以它实现宏(macros)。宏看起来像函数,但不会招致函数调用带来的额外开销。
//以a和b的较大值调用f
#defineCALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))
int a = 5, b = 0;
CALL_WITH_MAX(++a, b); //比较前a自增1次,比较后因返回a再次自增,a被累加二次!
CALL_WITH_MAX(++a, b+10); //比较前a自增1次,比较后因返回b,a只被累加一次
记住为宏中的所有实参加上小括号;但以上例子所带来的麻烦是“a的递增次数竟取决于它被拿来和谁比较”。
用template inline函数可以获得宏带来的效率以及一般函数的所有可预料行为和类型安全性(type safety):
template<typenameT>
inlinevoid callWithMax(const T& a, const T& b) //template c++,采用pass by reference-to-const.
{
f(a > b ? a : b);
}
这个template产出一整群函数,每个函数都接受两个同型对象,并以其中较大者调用f。这里不需要在函数本体中为参数加上括号,也不需要操心参数被核算(求值)多次……等等。此外由于callWithMax是个真正的函数,它遵守作用域和访问规则。例如你绝对可以写出一个"class内的private inline函数"。一般而言宏无法完成此事。
有了consts、enums和inlines,我们对预处理器(特别是#define)的需求降低了,但并非完全消除。#include仍然是必需品,而#ifdef/#ifndef也继续扮演控制编译的重要角色。