让自己习惯C++
条款01:视C++为一个语言联邦
将C++视为四个主要次语言的联邦
- C
- Object-Oriented C++
- Template C++
- STL
不同次语言的高效编程守则要求可能不同
条款02:尽量以const,enum,inline替代#define
define的坏处
不利于维护,安全性弱
1.用const取代
定义常量指针:注意const的放置
class专属常量:为确保常量至多只有一份实体,必须让它成为static成员:
class GamePlayer{
private:
static const int NumTurns=5;//常量声明式
...
};
//以上存在于头文件中
//以下存在于实现文件中
const int GamePlayer::NumTurns;//NumTurns的定义;
或者
class GamePlayer{
private:
static const int NumTurns;//常量声明式
...
};
//以上存在于头文件中
//以下存在于实现文件中
const int GamePlayer::NumTurns=5;//NumTurns的定义;
若它是个class专属常量又是static且为整数类型(integral type,例如int,char,bool),则在其不被后续操作取地址的情况下可以无须提供定义式
2.用enum取代
在class编译期间需要一个class常量值,例如
class GamePlayer{
private:
enum { NumTurns=5}; //“the enum hack”
int scores[NumTurns]; //使用该常量
};
//以上存在于头文件中
//以下存在于实现文件中
const int GamePlayer::NumTurns;//NumTurns的定义;
理论基础:一个属于枚举类型(enumerated type)的数值可权充int
与define相似的地方
无法取对应的地址(#define也得到,功能相似)
不想别人获得pointer或reference指向该资源
3.用inline取代
某些时候,宏定义的函数会导向奇怪的结果
//以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被累加一次
可以用template inline的函数取代;因为inline的函数也相当于内部直接插入一段代码
template<typename T>
inline void callWithMax(const T& a,const T& b)
{
f(a>b?a:b);
}
总结:
对于单纯常量,最好以const对象或enums替换#defines
对于形如函数的宏,最好改用inline函数替换
条款03:尽可能使用const
1.const修饰指针时:
const出现在星号左边,表示被指物是常量;在右边表示指针自身是常量
const修饰迭代器时:
const iterator iter//类似T* const
const_iterator cIter//类似 const T*
2.将const实施于成员函数的好处
1.使class接口容易被理解,得知哪些函数可以修改对象内容,哪些不行。
2.pass by reference-to-const方式传递对象需要有const成员函数可用来处理得到(并经修饰而成)的const对象。
两个成员函数如果只是常量性不同,可以被重载
3.bitwise constness(编译器就是这种)和logical constness
bitwise constness是成员函数只有在不更改对象的任何成员变量时就认为是符合const,这个定义存在一定不足,因为即使编译器查不出更改也可能存在更改,比如改变指针所指
logical constness是一个const成员可以修改它所处对象内的某些bits,但只有在客户端侦测不出的情况下才可以
通过mutable关键字,可以释放掉non-static成员变量的bitwise constness约束,从而在const函数内也可以修改所在对象的mutable成员变量
mutable bool lengthIsValid;//会被更改,即使在const成员函数内
4.在const和non-const成员函数中避免重复
实现常量性转除。消除const的性质,具体的做法是在non-const的函数里调用对应的const函数,并实现强制转换(const_cast可以强制消除const属性,static_cast可以实现强制转换)
class TextBlock{
public:
...
const char& operator[](size_t position)const
{
...
return text[position];
}
char& operator[](size_t position)
{
return const_cast<char&>(
static_cast<const TextBlock&>(*this)[position]
);
}
};
总结
将某些东西声明为const可帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。
当const和non-const成员函数存在等价实现,用non-const函数调用const版本可以避免代码重复
条款04:确定对象被使用前已被初始化
对于无任何成员的内置类型,需要手工初始化
构造函数最好使用初始化列表,而不要在构造函数内赋值操作。初值列列出的成语变量,其排列次序应该和它们在class中的声明次序相同
要区分赋值和初始化,对于class的对象,在构造函数内赋值实际上是先调用了它们的默认构造函数,然后做赋值操作,比较低效
成员的初始化列表本质是调用复制构造函数,效率较高
规则: 总是在初值列中列出所有成员变量,以免还需记住那些成员变量无需初值
为了免除“跨编译单元之初始化次序”的问题(就是定义文件之间的初始化没有先后顺序),应该以local static对象替换non-local static对象
non-local static:函数外的static对象就是non-local static变量
问题:C++对于不同编译单元内的non-local static对象的初始化次序没有明确定义
解决:用singleton的思想。将每个non-local static变量搬到专属函数内,这些函数返回一个reference指向它所含的对象,然后用户调用这些函数,不直接调用对象。这时,non-local static变成了local static
这种reference-returning函数的形式:定义并初始化一个local static, 然后返回它
class FileSystem{
...
};
extern FileSystem tfs;
//替换如下
FileSystem& tfs()
{
static FileSystem fs;
return fs;
}
总结:
无任何成员的内置类型,需要手工初始化
构造函数最好使用初始化列表,且列出的成员变量次序应该和它们在class中的声明次序相同
为免除“跨编译单元之初始化次序”问题,请以local static 对象替换 non-local static对象。