取自《Effective C++:改善程序与设计的55个具体做法》中文版第三版
一、让自己习惯C++
01:视C++为一个语言联邦
将C++视为一个由相关语言组成的联邦,其主要的次语言有四个:
- C:以C为基础。
- Object-Oriented C++:面向对象设计。
- Template C++:泛型编程。
- STL:template程序库。
- C++高效编程守则视状况而变化,取决于使用C++的哪一部分。
02:尽量以const,enum,inline替换#define
- const
1 |
|
该宏定义,记号名称 ASPECT_PATIO 有可能没有进入记号表,运用该常量出现错误时可能不会直接指示该名称;此外,预编译器替换名称可能导致目标码出现多个1.653;采用常量定义没有这两个问题;
对于class专属常量,确保此常量只有一份实体,需要成为static成员;#define 无法创建class专属常量,实例如下:
1 | class GamePlayer { |
- enum
有的旧编译器不支持声明时赋值,然而上述的 GamePlayer::score数组声明需要一个常量值,这时可采用enum替换:
1 | class GamePlayer { |
注意,取一个 const 的地址合法,但是取一个 enum 或 #define 的地址则不合法。
- inline
1 |
|
对于该示例,宏不是函数,不会有函数调用带来的额外开销;宏中所有的实参需要加上小括号;对于使用宏带来的问题,可采用 template inline实现,如下:
1 | template<typename T> |
- 对于单纯常量,最好以 const 对象或 enunms 替换 #define。
- 对于形似函数的宏,最好改用 inline 函数替换 #define。
03:尽可能使用const
- const 指针
1 | char greeting[] = "Hello"; |
const 允许指定一个语义约束,而编译器会强制实施该约束,使某值保持不变;
STL迭代器的作用类似 T* 指针,如果声明为const表示该指针的常量,如果使得指向的值为常量,需使用const_iterator,如下:
1 | std::vector<int> vec; |
- const 成员函数
将const作用于成员函数,可使class接口易于理,且使“操作const对象”成为可能;
1 | class CTextBlock { |
上诉问题的解决办法可添加 mutable,如下:
1 | class CTextBlock { |
对于是否带有 const 可实现重载,对于出现的代码重复,可通过转型避免。如下所示:
1 | class TextBlock { |
该代码有两个转型动作,第一个,将原始类型 TextBlock& 转换为 const TextBlock&,从而得以调用 const operator[];第二次则从返回值中移除const。对于反向做法:令 const 版本调用 non-const 版本,则是一种错误行为,存在安全性风险。
- 将某些东西声明为 const 可帮助编译器侦测出错误用法。
- 编译器强制实施 bitwise constness,但编写程序应使用“概念上的常量性”。(实在无法理解原文中这些概念的描述)
- 当 const 和 non-const 成员函数等价时,令 non-const 调用 const 版本可避免代码重复。
04:确定对象被使用前已被初始化
1 | inx x = 0; //int手工初始话 |
读取未初始化的值会导致不明确的行为,通常 C part of C++ 不保证发生初始化;
对于内置类型以外的东西,初始化通过构造函数实现,但需要分清赋值和初始化,如下实例:
1 | class PhoneNumber { ... }; |
赋值的构造函数实际为先调用默认构造函数,再进行赋值操作,因此使用成员初值列来初始化的方式要高效的多;对于内置对象如 numTimesConsulted,则没有区别。
C++的成员初始化顺序往往是固定的,以其声明的顺序被初始化,与初值列的顺序无关;此外,对于如果一个成员变量的初始化需要依赖另外一个成员变量,如数组的长度,则那个成员变量需要先有初值。
函数内的 static 对象被称为 local static 对象,其他的 static 对象被称为 non-local static 对象,程序结束时 static 对象会自动销毁。编译单元是指产出单一目标文件的那些源码。
问题:某一个编译单元某个 non-local static 对象的初始化用到了另一个编译单元的 non-local static 对象,它所用到的这个对象可能尚未被初始化。
这里采用的设计是将每个 non-local static 对象搬到自己的专属函数内,该对象被声明为 static,函数返回一个指向该对象的指针,然后用户调用这些函数,而不是这些对象,即 non-local static 对象 被 local static 对象替换了。这类函数称为 reference-returning 函数。示例如下:
1 | //第一个编译单元 |
然而,这种写法使得在多线程中带有不确定性,一种处理方法是在单线程启动阶段手工调用所有 reference-returning 函数;另外需要的则是加强设计以避免这类情况。
为内置对象进行手工初始化,因为 C++ 不保证初始化它们。
构造函数最好使用成员初值列,而不是在构造函数内使用赋值操作。
为避免“跨编译单元之初始化次序”问题,请以 local static 对象代替 non-local static 对象。