Effective C++ 笔记
《Effective C++》第三版的笔记
让自己习惯C++
尽量以const, enum, inline
替换 #define
#define
是预处理命令,预处理器只会对文本进行简单的替换,不会进行类型检查等等操作。同时,预处理命令定义的变量并不会在编译阶段出现,因此当这个预处理命令出问题时,很难进行排查和定位。
const
用来替换常量,inline
用来替换 #define
而enum
则是为了补充 const
的一个使用场景。
当在类内定义一个静态的常量时,这个常量必须要在类外才能初始化。
1 |
|
尽可能使用const
重载
注意,两个函数参数的 const
不同,这两个函数不能被重载,也就是说
1 |
|
这两个函数在编译器眼中是一样的
而const
成员函数则可以重载
1 |
|
bitwise constness 和 logical constness
对const
来说,有两种
- bitwise constness:对于这个常量,它的二进制不改变
- logical constness:对于这个常量,它的外在表现不会改变
编译器执行的是 bitwise constness,而写程序时就需要保证是 logical constness。
当类里面出现了指针,就会出现两种 const 不同的情况
1 |
|
const
和non-const
避免重复
当const
成员函数和non-const
成员函数有相同的行为时,可以让non-const
调用const
版本实现代码的复用
1 |
|
确定对象被使用前已经初始化
尽量使用列表初始化,这样效率更高
为了免除编译时多个文件初始化顺序的问题,尽量使用 local static
对象代替 non-local static
对象
构造、析构赋值
编译器默默编写的函数
1 |
|
注意:这些函数一开始都没有,只有需要用到的时候才会被构造出来
不使用自动生成的函数,就应该拒绝
比如说单例模式,如果不删掉或者自由化拷贝构造函数,编译器就会自动生成,然后就会违反单例模式。
将基类析构函数声明为虚函数
老面试题了,核心问题是当父类指针指向基类的对象时,会发生内存泄露
不要在构造函数和析构函数中调用虚函数
因为子类的在构造自身前需要调用基类的构造函数,此时基类中构造函数调用的虚函数是基类版本的虚函数,会很难排查。
一种可能的方式是,这样会发生混乱
1 |
|
operator=
返回一个reference to *this
这样就可以做到连续赋值了 test3 = test2 = test1
1 |
|
在operator=
中处理自我赋值
对用户来说,很可能发生自己给自己赋值的情况,当进行删除操作的时候,需要额外的注意,可以采用
比较源和目标的地址
调整语句顺序(让删除操作在构造新对象之后进行)
1
2
3
4
5
6Test &operator= (const Test &others) {
Data *temp = other.data;
data = new Data(temp);
delete temp;
return *this;
}copy-and-swap(较好的方法)
复制时不要忘记每一个成员
当在派生类写复制构造函数或赋值运算符重载函数的时候,经常会忘记构造基类的对象,应该在派生类的函数中调用基类对应的函数
1 |
|
资源管理
资源用了要还回去,但是还回去并不简单,很容易出现资源的泄露。
以对象管理资源
以对象管理资源有两个关键的思路
- 资源获取后立即放入管理对象中
- 管理对象利用析构函数确保资源的释放
为了防止资源泄露,尽量使用RAII对象
在资源管理类中小心对象拷贝
资源很多时候是互斥的,当对一个RAII对象进行拷贝时,他们的行为会有不同,主要包括
- 禁止拷贝(
unique_ptr
) - 允许拷贝,通过引用计数,确保安全释放(
shared_ptr
) - 复制底部资源
- 转移底部资源的拥有权
在资源管理类中提供对原始资源的访问
RAII对象很好,但是有些API只允许传入原始的对象,如指针,因此需要提供一个访问原始资源的接口。可以通过函数显示的调用,也可提供隐式类型转换
使用new
和delete
要使用相同的形式
delete
用 new []
构造的对象会造成资源的泄露
delete[]
用 new
构造的对象会造成未定义的行为
通过new
构造的智能指针需要设置为独立的语句
对于下面的语句
1 |
|
编译器的执行顺序为下图,但是 new Person
和 getNumber
的执行顺序无法确认。
graph LR
执行newPerson --> 调用shared_ptr构造函数 --> 执行setNumber
调用getNumber --> 执行setNumber
当 new Person
执行后再执行 getNumber()
此时一旦 getNumber()
中出现异常,new
出的对象将会造成内存泄露
设计与声明
让接口容易正确使用
- 构造新类型避免接口被误用
- 对输入进行限制
- 让自定义类型的行为与内置类型的行为一致
- 消除用户管理资源的责任
用传常量引用代替传值
- 对于自定义对象,使用传常量引用代替传值
- 对于内置类型和STL迭代器和函数对象,使用传值的方式就更加高效一些