习惯C++
将C++视为一个语言Federation
一开始C++只是C加上一些面向对象特性。C++最初的名称C with class也反映了这个血缘关系。
今天的C++已经是个多重范式编程语言(multiparadigm programming language),一个同时支持过程式(procedual)、面向对象(oo)、函数式(functional)、泛型形式(generic)、元编程形式(metaprogramming)的语言。
可以把C++视作一个相关语言组成的Federation而不是单一语言。在其某个次语言(sublanguage)中,各种守则与通例都倾向于简单、直观易懂、并且容易记住。
尽量以const
、enum
、inline
替换#define
或者说“宁可以编译器替换预处理器”。
#define
定义的名称并没有进入符号表。当你运用此常量但获得一个编译信息时,可能会给Debug带来困扰,特别是当这个常量定义在非你所写的头文件里的时候。
#define
不仅不能够用来定义class专属的常量,也不能够提供任何封装性。建议使用const
或者enum
。
对于函数宏,最好改用inline
函数替代。既可以获得宏带来的的效率,有可以获得一般函数的所有可预料性和类型安全性。
尽可能使用const
const允许指定一个语义约束(也就是一个不该被改动的对象),而编译器会强制实施这项约束。它允许你告诉编译器和其他程序员某值应该保持不变。
char greeting[] = "Hello";
char * p = greeting; //非const指针,非const值
const char * p = greeting;//非const指针,const值
char * const p = greeting;//const指针,非const值
const char * const p = greeting;//const指针,const值
如果const出现在星号左边,表示被指物是常量。
如果const出现在星号右边,表示指针自身是常量。
- 函数声明中的const
const返回值:函数返回一个const值,可以减少一些因客户错误造成的意外。
const成员函数:将const实施于成员函数的目的,是为了确认该成员函数可作用于const对象身上。(const函数一般不会修改对象本身)
const参数:没什么特别的,同const变量。
- 成员函数的bitwise constness和logical constness
bitwise constness阵营认为成员函数只有在不更改对象的任何成员变量时才可以说是const。
logical constness派认为一个成员可以修改所处对象的某些部分,但是只有在客户端检测不出的情况才得如此。
- mutable关键字
假如出于某些原因需要修改一个const对象的部分字段。但是编译器不同意,它们坚持bitwise constness。这时候就可以用mutable
关键字,去除成员变量的bitwise constness约束。
- 在const和non-const成员函数中避免重复
CRE:由于const和non-const可重载,所以要避免代码重复,可以用一个调用另一个。
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]
);
}
}
确定对象被使用前已先被初始化
读取未初始化的值会导致不明确的行为。在某些平台上,仅仅只是读取未初始化的值,就可能让你的程序终止运行。更可能的情况是读入一些“半随机”bits,污染了正在进行读取动作的哪个对象,最终导致不可测的程序行为。
C的array不保证其内容被初始化,而vector却有此保证。
- 内置类型的变量初始化
对于无任何成员的内置类型,必须手工完成初始化。
int x = 0;//手工初始化
- 类对象及其成员
内置类型以外的任何其他东西,初始化责任落在构造函数身上。规则很简单:确保每一个构造函数都将对象的每一个成员初始化。
//1
AAA(string& name)
{
member1 = 123;//这是赋值
}
//2
AAA(string& name) : member1(123)//这才是初始化 (成员初始列)
{}
构造函数中的初始化和赋值不能混淆。虽然最终结果相同,但初始化通常效率更高。基于赋值的版本首先调用default构造函数为成员设初值,然后立刻对它们赋予新值。default构造函数的一切作为因此浪费了。如果成员变量是const或者references,它们就一定需要初值,不能被赋值。所以最简单的做法就是:总是使用成员初始列。
C++有着十分固定的“成员初始化次序”。baseClasses总是更早于其derivedClasses被初始化。而class的成员变量总是以声明次序被初始化。
- 全局static变量的初始化
所谓static对象,其寿命从被构造出来直到程序结束为止。排除了stack和heap对象。
函数内的static对象称为local-static,其他的static对象称为non-local-static对象。
static对象有时可能存在跨编译单元的依赖。但是不同单元的static对象初始化次序是无法保证的。这时候需要把每个non-local-static对象搬到自己的专属函数内。也就是说non-local-static对象被local-static对象替代了。(这是单例模式的一种实现方法)
Directory& tempDir()
{
static Directory td;
return td;
}
这种结构在多线程系统中带有不确定性。(任何一种non-const static对象,无论它是local还是non-local,在多线程下等待某事发生都会有麻烦)
处理这个麻烦的一种做法是,在程序的单线程启动阶段手工调用所有reference-returnming函数,这可消除与初始化有关的竞争条件(race conditions)。
(END)