《Effective C++》复习笔记
一、习惯C++
减少宏替换
- 使用宏其替换的宏预处理期间就进行了字符串替换,被替换的宏不会被编译器看到,宏并没有进入符号表里。
所以单纯常量使用enum或者const对象代替宏#define替换 - 宏替换是简单的字符串替换,如果是形似函数的宏,在进行字符串替换之后,无法进入调试的。
所以最好改用inline函数,可以调试,还有参数检查
尽量使用const
- 可以帮助编译器侦测出错误用法
二、构造/析构/赋值
对象初始化
- 使用对象前要进行初始化,构造函数最好使用成员初值列,而不是在构造函数中赋值操作,初值列的成员变量顺序应该和class中声明的次序相同。
编译器自动生成的类成员函数
- 在没有明确声明构造函数和析构函数的时候,编译器会自动生成没参数的构造函数,拷贝构造函数,"="操作符,以及析构函数,如果不希望编译器自动生成,那么需要把上述的函数都声明为private并且不进行实现。
多态基类声明virtual析构函数
- 一个父类指针指向子对象析构的时候如果析构函数不是virtual形式,那父类指针离开作用域的时候只会调用父类的析构函数,在子类里面如果有申请内存,却没调用子类的析构函数,那就可能会造成内存泄露的现象,或者说其他错误。所以有继承形式的类一定要把析构函数声明为virtual。
析构函数不要抛出异常
- 如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,不再传播异常或者结束程序。析构函数如果抛一个异常,异常点之后的程序不会执行,如果析构函数在异常点之后执行了某些必要的动作比如释放某些资源,则这些动作不会执行,会造成诸如资源泄漏的问题。
构造函数析构函数中不要调用virtual函数
- 一个对象的构造顺序父类构造函数->子类构造函数,如果构造函数中调用父类的virtual函数,这个函数不会去调用对应子类的virtual函数的。
重载赋值运算符"="
- 赋值操作符应返回引用
要处理自我赋值问题
- 要确保复制对象的所有成员变量,以及base class部分
三、资源管理
防止内存泄露使用智能指针管理对象
- RAII全称是“Resource Acquisition is Initialization”,直译过来是“资源获取即初始化”,也就是说在构造函数中申请分配资源,在析构函数中释放资源
复制智能指针要一并复制管理的资源对象
- 常见管理方式:抑制拷贝(unique_ptr),引用计数(shared_ptr)
成对使用new delete
- new[] 对应delete[], new 对应 delete
将new的对象以独立语句放入智能指针中
- 加入放入参数里可能会造成难以察觉的内存泄露
四、设计与声明
让设计的接口不容易被误用
- 方法:建立新类型、限制类型上的操作、束缚对象值、消除调用者的资源管理责任
定义新类需要考虑
- 创建与销毁方式、初始化和赋值、拷贝、合法值检查、继承关系、类型转换、操作符重载是否合理、成员的公有私有属性、未声明接口、是否可以模板化
const引用传递代替以值传递
- 减少临时对象的构造与析构次数
函数返回对象要返回值而不是引用
- 不要返回局部变量指针或者引用,因为出了函数体就析构了。
将成员变量声明为private
- 可以提供public接口访问成员变量,保证数据访问的一致性、可划分访问控制,允许约束条件的保证
五、实现
延后变量定义时机
- 尽量延后变量定义式的出现时间,增加程序清晰度改善程序效率
尽量少做转型动作
- 注重效率的代码减少转型
- 使用转型语法 > 强转 > 隐式转换
避免返回引用指针迭代器指向对象内部
- 可以加const返回常量
inline的使用
- 内联函数应该声明在小型、频繁调用的函数上,减少潜在代码膨胀问题,提升程序速度
降低文件编译的依存关系
- 类里面如果可以使用对象指针就不要直接声明个对象
- 头文件尽量只有声明不要有定义、句柄类可以实现
六、继承与面向对象设计
继承关系是is-a
- 确定好子类对象是父类对象的一种再使用继承关系
注意子类对父类的直接隐藏
- 子类重写非虚函数会隐藏父类的方法、可以使用using Base::f()或者调用父类的方法来改善
区分接口继承和实现继承
- 纯虚函数只有接口继承
- 非纯虚函数有接口继承和缺省实现
- 非纯虚函数有接口继承和强制实现继承(只能调用父类的方法)
绝不重新定于继承来的缺省参数值
- 因为缺省参数值为静态绑定,无法正确动态绑定
慎用private继承
- 当子类需要访问父类保护成员或者需要重新定义继承来的virtual函数的访问权限时可以这样设计
慎用多重继承
- 核能会出现菱形继承,二义性,可以使用virtual继承解决。
- virtual继承会增加大小速度、初始化赋值复杂度等成本
- 在public继承一个接口类和private继承某个协助类时可以使用多重继承
七、模板和泛型编程
隐式接口和编译期多态
- 类继承和模板都是支持接口和多态的形式
- 对类而言的接口时显式,而模板的接口是隐式的
- 对类的多态发生于程序运行时期,而模板是程序编译时期,模板通过模板函数的具现化和函数重载解析实现多态
typename 和 class
- 声明模板参数时,前缀关键字class和typename可以互换
将参数无关的代码抽离出模板
- 减少代码膨胀