Item 1:视 C++ 为一个语言联邦
C++ 中的 4 个次语言:
① c
② Object-Oriented C++
③ Template C++
④ STL
Item 2:尽量以 const
、enum
、inline
替换 #define
- enum hack: 一个属于枚举类型的数值可权充 ints 使用
class GamePlayer
{
private:
enum { NumTurns = 5 };
int scores[NumTurns];
};
- “对于单纯常量,最好以
const
对象或enums
替换#define
”。 - “对于形似函数的宏,最好改用
inline
函数替换#define
”。
Item 3:尽可能使用 const
-
“当
const
和non-const
成员函数有着实质等价的实现时,令non-const
版本调用const
版本可避免代码重复。”例如下面的代码,可以运用 const 成员函数实现出其 non-const 孪生兄弟。
class TextBlock{ public: ... const char& operator[](std::size_t position) const { ... ... ... return text[position]; } char& operator[](std::size_t position) { // 省略的代码同上 ... ... ... return text[position]; } private: std::string text; }
改为如下代码:
class TextBlock{ public: ... const char& operator[](std::size_t position) const { ... ... ... return text[position]; } char& operator[](std::size_t position) { // 为 *this 加上 const,调用 const op[] return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]); } private: std::string text; }
上面的代码有两次转型,第一次用来为
*this
添加const
(这使接下来调用operator[]
时得以调用const
版本),第二次则是从const operator[]
的返回值中移除const
。-
static_cast<const TextBlock&>(*this)
:将当前对象*this
转换为const TextBlock
的引用,确保后续操作可以访问TextBlock
类的内容,但不允许修改。 -
(*this)[position]
:使用下标运算符访问TextBlock
类的一个元素,通常这应该返回一个const char
类型的引用,表示该位置的字符。 -
const_cast<char&>(...)
:使用const_cast
去掉返回的const
限定符,使得你可以得到一个可修改的char&
引用。这样就可以修改该位置的字符。
-
Item 4:确定对象使用前已先被初始化
-
“为内置类型对象进行手工初始化,因为 C++ 不保证初始化它们。”
-
“构造函数最好使用成员初值列表,而不要在构造函数本体内使用赋值操作。初值列表列出的成员变量,其排列次序应该和它们在 class 中的声明次序相同。”
-
C++ 有十分固定的 “成员初始化次序”。
- base classes 更早于其 derived classes 被初始化
- class 的成员变量总是以其声明次序被初始化
-
C++ 规定,对象的成员变量的初始化动作发生在进入构造函数本体之前,在函数体内的动作是赋值。
- 如果不使用初值列表,会首先调用默认构造函数为成员变量设初值,然后再调用拷贝赋值操作符对它们赋予新值。相比之下,单只调用一次拷贝构造函数是比较高效的,有时甚至高效的多。
-
-
“为免除“跨编译单元的初始化次序”问题,请以 local static 对象替换 non-local static 对象。”
下面两段代码来自不同编译单元:
class FileSystem{ public: ... std::size_t numDisks() const; // 众多成员函数之一 ... }; extern FileSystem tfs; // 预备给客户使用的对象
class Directory{ public: Directory(params); ... }; Directory::Directory(params) { ... std::size_t disks = tfs.numDisks(); // 使用 tfs 对象 ... } Directory tempDir(params);
除非 tfs 在 tempDir 之前被初始化,否则 tempDir 的构造函数会用到尚未初始化的 tfs。但是C++ 对“定义于不同的编译单元内的 non-local static 对象”的初始化相对次序并无明确定义。
解决办法:
将每个 non-local static 对象搬到自己的专属函数内(该对象在此函数内被声明为 static)。这些函数返回一个 reference 指向它所含的对象。然后用户调用这些函数,而不直接指涉这些对象。这个方法的基础在于:函数内的 local static 对象会在 “该函数被调用期间” “首次遇上该对象的定义式” 时被初始化:
// 两个类同前 ... FileSystem& tfs() { static FileSystem fs; return fs; } Directory::Directory(params) { ... std::size_t disks = tfs().numDisks(); // 使用 tfs 对象 ,改为 tfs() ... } // 这个函数用来替换 tempDir 对象 Directory& tempDir() { static Directory td; return td; }