1. 视C++ 为一个语言联邦

C++并不是某种特定语言,应该把它视为以下四种part of C语言的集合:

  • C
  • Object-Oriented C++
  • Template C++
  • STL

C++ 的高效编程指南会视情况而变化,取决于你使用了以上四个的哪一部分。

2. 尽量以const,enum,inline替换#define

核心原因

#define无视作用域且不进入符号表,导致调试困难且破坏封装性。

原因解释

#define被作用于预处理阶段,它可能从未被编译器看到。所以自然就不会进入记号表内。这会导致当你定义了以下一个宏: #define ACCD_BG 1.62 时,如果发生了某些编译错误,你只会在编译信息里看到 1.62 而不是 ACCD_BG

#define 无法提供封装性,因为它并不重视作用域。 一旦宏被定义,它就在其后的编译过程中有效,除非在某处被#undef

其他

  • string对象通常比char* 更合适。
  • 提到的enum hack方法,在C++11+项目应使用[[constexpr]]
  • C++一般总是要求你对使用的任何东西提供一个定义式。

3. 尽可能使用const

核心原因

const成员函数承诺不修改对象状态,但通过指针仍可能修改数据(如获取成员地址后修改)。mutable修饰符允许在const成员函数中修改特定成员,实现逻辑const与物理const的分离。

原因解释

const修饰成员函数的一大作用是保证这个函数不会修改类的非静态成员。 但是存在一个问题,即如果声明一个非const的指针来获取到了某个const修饰的成员地址。那你其实是可以通过这个指针来改变这片内存的。如果你用const A a, 且调用一个const修饰的成员函数,但你最终却还是通过一个指针p来改变了a的值。

即这种const成员函数的保证其实是不完全可靠的。

所以这里存在另外一种思维主张,即:即使是const修饰的成员函数,它也可以修改类成员的某些bit。你需要为这些可能被修改的成员前添加mutable修饰。

最佳实践

  • 当你可以用const,请用
  • 对于那些可能被修改的成员,请添加mutable来显式修饰。

4. 确保对象在使用前已经先被初始化

核心原因

C++并没有在所有语境保证会把你定义的对象进行默认初始化。 初始化列表避免伪初始化开销。

原因解释

1

int x; 有时候它会被初始化为0,有时候并不会。

2

在构造函数中赋值的方法实际上是一种伪初始化,对象被实际调用default构造函数的时间比进入类的构造函数更早。 对于内置对象(如int)则不一定,它不保证在构造函数之前一定被赋初值。 所以如果使用在构造函数中赋值的方式, default构造函数所做的一切其实被浪费了。 故初始化列表是一种更高效的方式。

最佳实践

  • 永远在使用对象之前先把它初始化。
  • 对于类的构造函数初始化,应该使用初始化列表的方式,而不是在构造函数中去给成员赋值。