条款一:视C++为一个语言联邦
今天的C++是个多重泛型编程语言(multiparadigm programming language),一个同时支持过程(procedural)、面向对象(object-oriented),函数形式(functional),泛型形式(generic),元编程形式(metaprogramming)的语言。
C++主要的次语言:
C。
Object-Oriented C++。class、封装、继承、多态等。
Template C++。C++泛型编程部分。
STL。容器、迭代器、算法以及函数对象
条款二:尽量以 const、enum、inline 替换 #define
#define
是在预处理阶段处理的,记号从未被编译器看见,所使用的名称也可能从未进入记号表(symbol table)。
解决方法是以一个常量替换宏。
有一个特殊情况是 class 专属常量。将常量的作用域限制在类内,必须让它成为 class 的 一个成员;为了确保此常量至多只有一份实体,必须让它成为 static
成员。
1 |
|
通常 C++ 要求你对你使用的任何东西提供一个定义式,对于类内静态整形变量,要在实现文件 .c 中提供定义。
1 |
|
这几乎是你唯一需要做的事情。唯一例外是当你在 class 编译期间需要一个 class 常量值,万一你的编译器不允许“static整数型class常量”完成“in-class 初值设定”,可改用所谓的“The enum hack” 补偿做法。其理论基础是:一个属于枚举类型的数值可权充 ints 被使用。
1 |
|
认识 enum hack:
enum hack 的行为某方面来说比较像
#define
而非const
。取一个const
地址是合法的,但#define
和enum
则不合法。纯粹为了实用主义,事实上,enum hack 是模板元编程的基础。
另一个常见的用法是用 #define
实现宏,看起来像函数,但没有函数调用的开销,但是这有很多缺点,有时候会产生一些不可预料的行为。解决方法是写出 template inline
函数。
小结:
对于单纯变量,尽量以 const 对象或者 enum 替换 #define。
对于形似函数的宏,最好改用 inline 函数替换 #define。
条款三:尽可能使用 const
const
允许你告诉编译器或其他程序员某值应该保持不变,只要这是事实,就该说出来。
const
出现在 * 两边分别代表什么呢 大专栏 让自己习惯C++?
声明迭代器为 const
就像声明指针为 const 一样,表示迭代器不得指向不同的东西,但它指的东西的值是可以改变的。如果想让它指的东西的值不能变,则要用 const_iterator
。
const
最具威力的用法是面对函数声明时的应用。在一个函数声明式内,const
可以和函数返回值、参数、函数自身(成员函数)产生关联。
const 成员函数,是为了确认该函数可作用于const
对象身上。两个关键原因:第一,使 class 接口比较容易被理解,哪些函数可以改动对象内容,哪些不可以;第二,使操作const
对象成为可能。
两个成员函数如果只是常量性不同,可以被重载。这是一个重要的C++特性。
真实程序中,const
对象大多用于 passed by pointer-to-const 或者 passed by reference-to-const 的传递结果。
同时实现 const
版本和non-const
版本的成员函数,如何避免代码重复?可以用 const
版本实现出 non-const
版本,即用const
成员函数实现出其non-const
孪生兄弟。
反向做法,即令 const
版本调用non-const
版本以避免重复是不对的。如果在const
成员函数内调用了non-const
版本的,则不保证绝不改变对象的逻辑状态。
小结:
将某些东西声明为
const
可帮助编译器侦测出错误用法。const
可被施加于任何作用域的对象、函数参数、返回值、成员函数本体。编译器强制实行 bitwise constness,即成员函数只有在不更改对象之任何成员变量(static变量除外)时才可以说是
const
,但你编写程序时应该使用“概念上的常量性”。当
const
和non-const
成员函数有着实质等价的实现时,令non-const
版本调用const
版本可避免代码重复,反之不允许。
条款04:确定对象被使用前已被初始化
读取未初始化的值会导致不明确的行为。
对于无任何成员的内置类型,必须手工完成此事。对于内置类型以外的任何其他东西,初始化责任落在构造函数上,规则就是:确保每个构造函数都将对象的每一个成员初始化。
这个规则很简单,重要的是别混淆了初始化和赋值。C++规定,对象的成员初始化动作发生在进入构造函数本体之前。
构造函数的一个较佳写法是,使用成员初始值列表替换赋值动作。对大多数类型而言,比起先调用default
构造函数,再调用 copy assignment
操作符,单只调用一次copy
构造函数是比较高效的。对于内置类型来说,效率是一样的,但为了形式统一,最好也通过初始值列表来初始化。
C++有着十分固定的“成员初始化次序”。base classes
总是早于其 derived classes
被初始化,而 class
的成员变量总是按照声明的次序初始化,即使在成员初始化列以不同次序出现。
C++ 对“定义于不同编译单元的non-local static
对象”的初始化次序并无明确定义。因为决定它们的次序非常困难。
消除这个问题的方法是:将每个 non-local static
对象搬到自己专属的函数内(该对象在此函数内被声明为static
)。这些函数返回一个reference
指向它所包含的对象,然后用户调用这个函数,而不直接指涉这些对象。换句话说,non-local static
对象被local static
对象替换了。这种结构下的reference-returning
函数往往十分单纯:第一行定义并初始化一个local static
对象,第二行返回它。
C++保证,函数内的local static
对象会在“该函数被调用”,“首次遇上该对象定义式”时被初始化。
任何一种non-const static
对象,不论它是local
或non-local
,在多线程环境下“等待某事发生”都会有麻烦。处理这个麻烦的一种做法是:在单线程启动阶段手工调用所有reference-returning
函数,这可消除与初始化有关的竞态条件。
小结:
手工初始化内置型对象;
构造函数最好使用成员初始化列表,而不要在构造函数中使用赋值操作。初始值列表列出的成员变量,其排列次序应该和它们在类中的声明次序相同;
为免除“跨编译单元之初始化次序”的问题,请以
local static
对象替代non-local static
对象。