《Effective C++》第三版——让自己习惯C++

参考资料:

  • 《Effective C++》第三版

注意:《Effective C++》不涉及任何 C++11 的内容,因此其中的部分准则可能在 C++11 出现后有更好的实现方式。

条款 1:视 C++ 为一个语言联邦

可以将 C++ 视作一个由多个次语言 构成的语言联邦,不同次语言 可能有不同的高效编程守则

C++ 中主要的次语言有 4 个:C、Object-Oriented C++、Template C++、STL。

条款 2:尽量以 constenuminline 替换 #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

constnon-const 成员函数功能相似时,令 non-const 版本调用 const 版本可以减少代码重复

constnon-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();
	}
};
相关推荐
奔跑吧邓邓子21 分钟前
【Python爬虫(34)】Python多进程编程:开启高效并行世界的钥匙
开发语言·爬虫·python·多进程
刃神太酷啦1 小时前
堆和priority_queue
数据结构·c++·蓝桥杯c++组
Heris991 小时前
2.22 c++练习【operator运算符重载、封装消息队列、封装信号灯集】
开发语言·c++
----云烟----1 小时前
C/C++ 中 volatile 关键字详解
c语言·开发语言·c++
yuanpan1 小时前
23种设计模式之《组合模式(Composite)》在c#中的应用及理解
开发语言·设计模式·c#·组合模式
BanLul1 小时前
进程与线程 (三)——线程间通信
c语言·开发语言·算法
十八朵郁金香1 小时前
【JavaScript】深入理解模块化
开发语言·javascript·ecmascript
Hello.Reader2 小时前
深入理解 Rust 的 `Rc<T>`:实现多所有权的智能指针
开发语言·后端·rust
程序员阿鹏2 小时前
jdbc批量插入数据到MySQL
java·开发语言·数据库·mysql·intellij-idea
yoona10202 小时前
Rust编程语言入门教程(八)所有权 Stack vs Heap
开发语言·后端·rust·区块链·学习方法