最近刚读完侯捷的《Effective C++》,相对来说,这本书的内容比较贴近基础,对于刚掌握C++基础的人会有不少的提高。不过书中还是涉及了不少C++的高级特性,阅读起来需要查阅相关的资料。书中给出了大量的示例和代码来说明具体规则的原理,我按照书中给出的标题将每个条目的关键内容整理如下。一方面是保留一份读书笔记,另一方面也是为了方便日后查阅方便。当然,如果不能从简单摘要的内容回忆起具体信息,到时再查书也不迟。同时也期望大家能从中找到自己没有注意的知识点,有所提高,大牛勿喷~ ☺。
(一)、让自己习惯C++
一、C++语言联邦
多重范型编程语言:过程式、面向对象式、函数式编程、泛型编程、模板元编程。
二、const、enum、inline替换#define
const:代替宏变量有助于编译器理解;
enum:enum hack,更像define,不消耗内存,无法取地址;
inline:宏函数尽量用inline代替。
三、const
const返回值:避免(a*b)=c的错误;
const参数:传递指向常量的引用;
const成员函数:允许const属性的重载。
四、对象使用前初始化
构造函数成员初始化列表;
使用时调用,单例模式,多线程不安全。
(二)、构造/析构/赋值运算
五、C++默认编写的函数
默认构造、复制构造、析构、赋值运算符。
六、拒绝自动生成的函数
私有化拷贝构造和赋值运算符;
私有继承UnCopyable手工类。
七、多态基类声明虚析构函数
(不)具有多态性质基类(不)需要虚析构函数;
八、不让异常逃出析构
异常时终止或者吞下;
将可能抛出异常的代码提供给用户管理;
九、不在构造和析构中调用虚函数
调用后仅仅是自身的虚函数,而非子类;
需要子类构造信息解决方案:子类使用静态函数构造基类的参数。
十、operator=返回*this的引用
允许连续赋值。
十一、operator=处理自我赋值
注意资源的释放顺序。
十二、复制对象要面面俱到
不要丢失基类的成员的复制。
(三)、资源管理
十三、对象管理资源
构造函数获得资源,析构函数释放资源;
使用智能指针封装:tr1::shared_ptr和auto_ptr。
十四、资源管理中小心copying
互斥锁加解锁的对象禁止复制;
引用计数法,tr1::shared_ptr<Mutex> mutex_ptr(pm,unlock),含有删除器的指针。
十五、资源管理类提供原始资源访问
原始资源获取;
隐式转换——类型转换函数。
十六、new-delete同型成对
[]的出现与否要对应起来,即使使用了typedef重命名了数组类型。
十七、独立成句的new对象放入智能指针
将new对象转换为智能指针作为参数,可能会被编译器结合其他参数调整顺序,造成内存泄漏。、
(四)、设计与声明
十八、让接口易用而不误用
类型一致性;
shared_ptr防范跨DLL错误。
十九、设计class犹如设计type
12条准则。
二十、常引用参数代替值传递
前者高效,但是对于内置类型除外。
二十一、需要返回对象时候不要返回引用
栈、堆、静态对象都不要作为引用返回。
二十二、成员变量声明为private
两种访问权限:private和others;
protected并不比public封装性好。
二十三、用非成员函数和非友元函数替换成员函数
封装强度和改变强度成反比,因为只影响有限的用户;
类外访问函数封装性好于累内成员函数的封装性,不增加累内私有数据的访问函数的数量;
二十四、参数需要类型转换应使用非成员函数
针对二元运算符重载。
二十五、没有异常的swap函数
类外构造特化的swap函数;
不要在swap的时候产生异常。
(五)、实现
二十六、延后变量定义式
不要提前定义,直到使用改变量的前一刻为之;
针对循环内的对象需要根据构造析构与赋值的成本,以及可维护性进行权衡。
二十七、少做转型操作
Base(*this).virFun()只会影响对象的基类部分的数据副本,不会影响对象本身,如果使用指针类型转换则会无穷递归,去掉虚属性则消除类似问题;
用虚函数的特性代替dynamic_cast;
尽量使用C++风格的转型。
二十八、避免返回对象内部数据的引用或指针
破坏了封装型;
函数返回对象析构导致空指针。
二十九、异常安全的努力
对象管理资源;
copy-swap实现技术;
异常安全性取决于最弱安全保证的代码。
三十、inline里里外外
隐式:累内直接定义成(友)员函数,显式:inline关键字;
拒绝:复杂、虚函数、函数指针调用、模板、构造析构函数、影响动态连接或升级、对调试器的挑战(禁用)。
三十一、降低文件间编译依存关系
能使用引用和指针完成的不使用对象、用class声明代替定义,并提供不同的头文件——程序库文件和类定义头文件;
handle class和interface class解除了接口与实现的耦合关系。
(六)、继承与面向对象设计
三十二、确定public继承塑膜出is-a关系
适用于基类的事情也适用于子类。
三十三、避免遮掩继承来的名称
基类的重载函数一旦在子类被重写后,其他的同名函数无法访问。
三十四、区分接口继承和实现继承
接口声明为纯虚函数,实现单独列出;
纯虚函数指定接口继承,虚函数指定接口和默认实现,一般函数指定接口和强制实现。
三十五、考虑虚函数以外的选择
私有虚函数在父类被调用的时候自动多态,基本保留何时调用的权力,子类拥有修改功能的权力;
function函数指针对象使得函数指针更加灵活;
古典策略模式:
使得不同的功能通过继承HealthCalcFunc改变。
三十六、绝不定义继承的非虚函数
重修继承的非虚函数导致函数的访问由指向对象的指针或引用类型决定。
三十七、绝不定义继承的默认参数值
重载的虚函数的默认参数来自于基类;
将默认参数函数声明为普通成员函数,调用私有的虚函数即可。
三十八、用复合塑膜出has-a和实现关系
has-a:对象的包含关系;
实现:对象对另一个对象进行具体特化。
三十九、审慎使用private继承
私有继承表达的是实现关系,子类使用父类提供的接口,但是不继承;
能用复合不用私有继承;
如何实现final字段:
这样Widget的子类就不会修改onTick函数了,将内部类移出,换做声明可以降低耦合;
private继承的空基类的大小实际为0,一般对象大小不能为0;
需要基类protected成员或者重写虚函数时候可以考虑使用private继承。
四十、审慎使用多重继承
使用虚基类导致速度变慢;
多重继承中使用公有继承继承接口,私有继承完成实现关系。
(七)、模板与泛型编程
四十一、隐式接口与编译多态
class是显示接口——函数签名,运行多态——虚函数;
template是隐式接口——有效表达式,编译多态——模板具体化与函数重载解析。
四十二、typename双重含义
模板声明中与class没有任何区别;
嵌套从属类型的显式指定,不能出现在基类列表和初始化列表中;
四十三、处理模板化基类名称
继承模板化基类的名称不能像继承一样使用:通过this->名字修饰、using 基类<T>::名字、或者基类<T>::名字一共三种修饰方式。第三种导致虚函数功能失效。
四十四、参数无关代码抽离模板
将与模板无关的非类型参数转移到类内;
尽量降低与模板无关的类型参数的膨胀度。
四十五、运用成员函数模板接受兼容类型
成员函数使用函数模板兼容更多类型;
函数模板声明后的copy构造和编译器生成的并不同,需要单独处理。
四十六、类型转换时为模板定义非成员函数
对于模板化的类要支持双操作运算符重载,首先必须是非成员函数,另外为了能让模板具体化必须将函数定在类体内部,因此只能将之声明为友元类型。(并非模板类内的友元函数必须类内定义)。
四十七、使用traits 类表现类型信息
STL五大迭代器:
1.输入迭代器:向前,一次一步,只读一次,istream_iterator。
2.输出迭代器:向前,一次一步,只写一次, ostream_iterator。
3.前向迭代器:向前,一次一步,可读可写多次,单向列表。
4.双向迭代器:向前向后,一次一步,可读可写多次,list、set、map。
5.随机迭代器:向前向后,一次多步,可读可写多次,vector、deque、string。
实现迭代器累加操作时候需要根据迭代器类型执行不同的操作方式,这种判断属于编译时期的判断,不应该使用if语句!
可以根据iterator_traits提供的类别标签区分迭代器类型,类别标签是空结构体类型,将标签作为函数参数,可以保证编译器能在编译时期对类型进行检查。
现在就可以把doAdvance封装起来自动完成编译期类型判断。
四十八、模板元编程
让某些事情变得容易可能,将某些工作从运行期转移到编译期;
分支——借由模板特化实现;
循环——借由递归完成;
优点:保证度量单位的正确、优化矩阵运算生成客户定制设计模式实现品;
避免了生成某些特殊类型不适合的代码。
(八)、定制new和delete
四十九、new-handler行为
set_new_handler指定内存分配失败时调用的函数。
五十、new、delete合理替换时机
改善性能,内存对齐,heap错误调试,收集heap信息。
五十一、new、delete固守常规
new含有无限循环分配内存,无法分配调用new-handler,处理0字节和超额申请;
delete处理null指针和超额申请。
五十二、写了placement new就要写placement delete
placement new在已有的缓冲区内申请对象;
不要掩盖已有的版本。