Accustoming Yourself to CPP

本书的更新截止至 2005 年

导读部分提到了 Declaration 和 Definition 的区别,谈论的对象包括 Object, Function, Class, Template。
提到了函数的 Signature。
提到了 Initialization,解释了什么是 Default Constructor,即可以不传入任何参数的形式被调用的构造函数。
提到了 Implicit Type Conversions 和 Explicit Type Conversions 以及 explicit 在声明构造函数时的作用。
提到了 Copy Constructor 和 Copy Assignment Operator 的区别。提到 Copy Constructor 在函数 Pass-by-Value 传参中被采用。

MyClass var1 (var2);
MyClass var1 = var2; // 调用复制构造函数
var1 = var3; // 调用赋值运算符

提到了两种 Undefined Behaviour:对空指针解引用,数组下标越界。

View C++ as a Federation of Languages

C++ 并不仅仅是一个 C with classes,而是一个 Multiparadigm programming language。同时支持

Prefer consts, enums and inlines to #defines

说的是常量的处理。本质上是以编译器替代预处理器。#define 并不被视为语言的一部分

常量可分为全局常量、命名空间内常量、类内静态常量、函数内静态常量。后两者添加静态修饰为了是防止(类在创建实例时,函数在调用时)出现多份。前三者如果定义在头文件中会导致导入其的源文件各自有一份该常量。前三者可以在头文件中 extern 加以避免(注意类内静态常量在定义时形式的特殊性),但需要在源文件中进行定义。

特殊地:

#define 实现宏的缺点(全是因为它直接替换的原因)

Use const whenever possible

const 的含义是指定一个“不应该发生改变的”语义约束,并驱使编译器为你保证这项约束。

const 拥有多种形式,常见于指针的多重 const 含义;也可以作用于各种地方:

const MyClass var;MyClass const var; 的写法是相同的。

函数的返回值是右值时,如果需要,使用 const type 而不是 type 可以避免对该右值进行赋值等带有改变性质的操作(通常是无意的,如 == 写成 =),因为通常右值没有 storage。

成员函数函数本身是 const 函数使得其可以作用于 const对象。

bitwise constness 和 logical constness 的争议

这里考虑的是成员函数的 constness。
bitwise 是说该对象操作物理上不改变该对象自身的任何一个 bit,C++ 的 const 采用的便是这种规定

logical 是说在客户端侦测不出来的情况下可以修改,也就是说从用户的视角看顶层,它是满足 constness 的,但是底层实现的许多地方需要非 const 操作,而且这些操作不影响顶层表征的 constness。如 CTextBlock 可能 cache 文本长度应付查询。这是应当在实际编程中采用的。
那么就应当有一种特性使得这些非 bitwise constness 的底层操作可以通过编译器的 constness 检查,mutable 修饰符应运而生。

避免重复

有时同时需要 const 和 non-const 两个版本,如 operator[]。const 和 non-const 成员函数的行为通常十分相似,重复一遍会造成代码膨胀,增加编译时间、维护成本。解决方法是让 non-const 版本调用 const 版本,重要的实现手段是 const_cast<>()。

Make sure that objects are initialized before they're used

不同语言体系中的标准不同:C part of C++ 中不保证发生初始化,如各种内置类型;但是 non-C part of C++ 中保证(由构造函数等来实现)。实际编程中应该采用 non-C part of C++ 的标准:对于内置类型进行手动初始化,对于用户自定义类型,编写良好的构造函数并进行良好的初始化。

读取未初始化的值是 UB。

不要混淆赋值和初始化。

初始化有两种手段:member initializer list 和 default initializer。后者存在的目的是减少在不同构造函数中的重复工作,在前者中经初始化的对象不会再经后者。

单个类成员初始化次序并不被 member initializer list 中的次序决定。
1) If the constructor is for the most-derived class, virtual bases are initialized in the order in which they appear in depth-first left-to-right traversal of the base class declarations (left-to-right refers to the appearance in base-specifier lists).
2) Then, direct bases are initialized in left-to-right order as they appear in this class's base-specifier list.
3) Then, non-static data member are initialized in order of declaration in the class definition.
4) Finally, the body of the constructor is executed.

不同编译单元(产出目标文件的 .cpp 加上 #include 的头文件)之间初始化次序有依赖的 non-local static 对象的初始化次序。C++ 标准对此是无定义的,原因包括决定次序的困难性:多个编译单元内 non-local static 对象经由 implicit templatae instantiations 形成。解决方法是使用 Singleton 模式中常见的用 local static 代替 global static,缺点是在多线程时进行初始化不够安全;没有显式指定初始化顺序的清晰性。