参考资料:
- 《Effective C++》第三版
注意:《Effective C++》不涉及任何 C++11 的内容,因此其中的部分准则可能在 C++11 出现后有更好的实现方式。
条款 1:视 C++ 为一个语言联邦
可以将 C++ 视作一个由多个次语言 构成的语言联邦,不同次语言 可能有不同的高效编程守则。
C++ 中主要的次语言有 4 个:C、Object-Oriented C++、Template C++、STL。
条款 2:尽量以 const
,enum
,inline
替换 #define
对于单纯常量,最好以 const
对象或 enums
替换 #define
通过 #define
定义常量,例如:
cpp
#define PI 3.14
PI
将在预处理阶段 被"移走",导致 PI
没有进入记号表、不能被编译器 看见,这可能给后续调试带来麻烦。
使用 const
替换 #define
,可以确保名字能被编译器看到:
cpp
const double pi = 3.14;
此外,使用 const
还能实现可以将常量的可见范围限定在某个作用域(如 class
内部),而 #define
不具备此功能。
在某些特殊情况里,我们也可以用 enum
实现 int
常量:
cpp
class A{
private:
// 这里的特殊情况指,某些编译器(错误地)不允许为static整型class常量提供类内初始值
enum { Num = 5 };
int arr[Num];
}
对于形似函数的宏,最好以 inline
函数替换 #define
使用 #define
定义宏可能会导致很多问题:
cpp
// 对较大者调用f
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b)) // 已经足够小心地为每个实参添加小括号
int a = 5, b = 0;
CALL_WITH_MAX(++a, b); // a累加2次
CALL_WITH_MAX(++a, b+10); //a累加1次
利用 template inline
可以兼顾效率和安全性:
cpp
template<typename T>
inline void callWithMax(const T &a, const T &b) {
f(a > b ? a : b);
}
条款 3:尽可能使用 const
将某些东西声明为 const
可帮助编译器侦测出错误用法
- 对于指针,
const
出现在*
前代表被指物是常量,出现在*
后代表指针本身是常量; - 对于迭代器,用
const
修饰一个迭代起类型,代表这个迭代器是常量,const_iterator
代表被指物是常量; - 对于函数参数,如果不要在函数内部对其进行修改,就将它们声明为
const
;
编译器强制实施 bitwise constness,但编写程序时还要注意 conceptual constness
设计 const
成员函数,不仅可以明确 地表示哪些成员函数可以改动对象内容 ,哪些不行,还可以使操作 const
对象成为可能。
需要注意的是,对于 const
成员函数,编译器只保证 bitwise constness ,即不更改对象的任何成员。然而,如果对象中包含指针,编译器却允许 const
成员修改指针所指的内容。另外,如果我们希望绕过 bitwise constness,可以将成员声明为 mutable
。
当 const
和 non-const
成员函数功能相似时,令 non-const
版本调用 const
版本可以减少代码重复
当 const
和 non-const
成员函数功能相似时,不应令 const
版本调用 non-const
版本,因为这会带来"对象被修改"的风险;而应令 non-const
版本调用 const
版本。
条款 4 确定对象被使用前已先被初始化
对内置对象进行手工初始化,因为 C++ 不保证初始化它们
C++ 的初始化规则较为复杂,只需要记住所有对象使用之前必须初始化,对于内置类型 ,必须手工完成此事。
构造函数最好是用成员初值列,顺序应与其在 class
中的声明顺序相同
为避免跨编译单元初始化次序问题,应以 local static
对象代替 non-local static
对象
考虑下面的情形:
cpp
class A {
public:
void f();
};
extern A a; // 预留给客户使用的对象
A a;
cpp
// 客户的文件
extern A a;
class B {
void g() {
a.f();
}
};
B b;
b.g(); // 不能确保g()被调用前,a已经初始化
C++ 对于定义在不同编译单元 的 non-local static 对象的初始化顺序并无明确定义 ,所以无法确保 b.g()
执行前 a
已经被初始化。
解决方法是,将 non-local static 对象的初始化放入函数中:
cpp
A& init(){ // 第一行定义static对象,第二行返回,可以考虑定义为inline
static A a;
return a;
}
cpp
class B {
void g() {
init().f();
}
};