<<Effective C++>>读书笔记(1)
2017-05-02 20:53
253 查看
一.让自己习惯C++
2.尽量以const,enum,inline替换#define:
a)当我们定义一个常量时,最好使用const常量,而非#define,因为define会无脑的替换,const常量可以是专属于某处的,而#define所定义的是全局的.
b)使用enum类似于#define,但是效果更好,而且enum也可以作为int类型的变量使用.
c)有时候我们会用#define来定义一个函数,这样做很方便而且没有调用函数的开销,但是这样的函数很容易出问题.而我们使用inline函数,加上模板可以更好地实现这种效果.
d)虽然#define可以被这么多种东西替代,但是#define还是必不可少的.#ifndef/#ifdef更是对编译流控制的好方法.
3.尽可能使用const:
a)防止意外修改不该被修改的内容,使之尽早在编译时暴露出来.const可以修饰任何对象,函数参数,函数返回类型,成员函数本体.
b)对于成员函数,如果不希望其改变对象,可以声明为const.
c)const版本和non-const版本的成员函数实现等价时,可以令non-const版本的调用const版本,使用const_cast去除const即可.
4.保证对象在使用前被初始化:
a)在使用对象或者变量前,保证其被初始化,防止出现随机值.
b)对于类的构造函数,最好使用成员初值列表(member initializetion list)来初始化,初值列的成员变量排列次序与声明次序相同(这个不是强制的,但是最好如是执行,比如我们申请一个数组,数组大小应该现有初值才行).而不要在构造函数内进行赋值操作,这样会先默认初始化再赋值,相当于两次操作.
c)跨编译单元的初始化顺序,以local static对象替换non-local-static对象.类似单例模式,我们在使用对象时,有时候不确定初始化的时机,那么我们不如在使用前,将要用的资源一起初始化了.这样就可以控制初始划顺序了.
二.构造,析构,赋值运算
5.了解C++默认编写并调用了哪些函数
a)如果我们什么都不写的话,满足一定条件(c++对象模型-默认构造函数的构造操作),编译器也会为我们生成默认构造函数,析构函数,拷贝构造函数,以及一个赋值运算符,
b)如果我们声明了这些函数,那么C++会使用我们自己编写的这些函数,不再使用哪那些默认的.
c)编译器为我们生成的默认构造函数是没有参数的,如果我们声明了其他的有参数的构造函数,那么这个默认构造函数也会消失,我们如果仍想要无参数的构造函数,那么就要自己再声明一个.
d)编译器为我们生成的析构函数,不会是virtual的,除非这个类的基类有virtual析构函数.
6.若明确不想使用编译器自动生成的函数,就应该明确拒绝
a)有时候我们不容许拷贝或者赋值一个对象时,即使我们不写拷贝或者赋值函数,编译器也会为我们生成一个,怎么办呢>答案是自己写一个private的拷贝构造函数和赋值函数,这样从类外就不能调用了.但是从类内还是可以调用的,这时候,我们可以只声明,不定义,这样,即使有人不小心使用了,在编译链接的时候,也会报错.把错误提前暴露,才容易发现,
b)如果我们怕忘记,或者为了方便,也可以定义一个基类uncopyable,让我们的类继承这个类即可.
7.为多态基类声明虚析构函数
a)如果我们不这样做,当使用基类指针控制子类对象时,当析构时会发生只析构基类部分,而子类部分不被释放的尴尬情况.导致内存泄露.
b)但是这也不代表着为了保险起见,我们全都要用virtual析构函数,因为虚函数的开销是比较大的,所以一般判定需要使用虚析构函数的条件为:只要这个类中有一个其他的虚函数,那么就使用虚析构函数.
c)既然要使用多态的特性,那么如果基类不是虚析构函数,那我们就不要去继承这个类,不然会引出一大串麻烦.比如string,标准stl等都是非析构函数的.
d)析构函数的调用顺序:子类的析构函数先调用,然后依次向上调用父类的析构函数.
8.别让异常逃离析构函数
a)能不抛出异常的,或者感觉会抛出异常的功能,不要放在析构函数中,额外设置一个函数.比如close()关闭函数.但是,在析构函数中仍然要做一下关闭的操作,防止客户忘记调用close发生异常.但是,在析构函数中抛出异常可能导致程序提前结束.应该在析构函数中就catch并处理.
b)被析构函数调用的函数如果抛出异常,析构函数也应该catch掉,保证不要再抛出.
c)如果客户需要对某个操作函数运行期间的异常做出反应,那么最好提供一个普通函数对运行期间抛出的异常做出反应,而不要在析构函数中执行该操作.
9.绝不
a7f9
在构造和析构函数中调用virtual函数
a)我们知道,如果有继承,构造函数会先调用父类的构造函数,而如果构造函数中有虚函数,此时子类还没有构造,所以此时的对象还是父类的,不会触发多态.
b)析构函数也是一样,子类先进行析构,如果析构函数内调用virutal函数的话,子类的内容已经被析构,C++会执行父类的virtual函数,不会出发多态.
c)总之,在构造和析构函数中,不要用虚函数.如果必须用,那么分离出一个Init函数和一个close函数,实现相关功能即可.
10.令operator=返回一个reference to *this
我们经常会为我们的类写一个operator=的函数,为了保证连锁赋值,比如x=y=z的这种情况能正常执行,最好是返回一个*this的引用.
11.在operator=中处理"自我赋值"
a)自我赋值看起来不是那么容易发生,但是程序复杂了什么情况都有,所以为了我们程序的健壮性,还是要处理一下的.
b)自我赋值会导致程序出错的情况在于,如果字段中有指针,先删除指针原来指向的对象,然后将指针指向=右边的对象.而自我赋值时会导致原来的对象被删除,而=右边的对象也被删除,因而会出错.
c)解决的方法其实最简单的就是先判定是否相等,如果和自己是同一个对象,直接返回*this.
12.复制一个对象时勿忘其每一个成分
a)正常情况下,如果我们不写拷贝构造函数,编译器会为我们生成一个默认的拷贝构造函数,但是这个函数只能实现浅拷贝,如果要实现深拷贝就必须要自己写一个拷贝构造函数.
b)但是如果我们自己写了拷贝构造函数,就一定要拷贝每一个成分,如果忘记了某个成分,编译器是不会提醒的.尤其在发生继承的时候,更加需要注意拷贝基类成员部分.通过调用基类函数来进行拷贝.
c)关于initialization和assignment:前者由构造函数执行,后者由operator=执行.两种对应不同的函数动作.两种可能有差不多额内容,但是不要使用一个调用另一个函数,如果实在想要抽象,不如提取一个Init函数.
2.尽量以const,enum,inline替换#define:
a)当我们定义一个常量时,最好使用const常量,而非#define,因为define会无脑的替换,const常量可以是专属于某处的,而#define所定义的是全局的.
b)使用enum类似于#define,但是效果更好,而且enum也可以作为int类型的变量使用.
c)有时候我们会用#define来定义一个函数,这样做很方便而且没有调用函数的开销,但是这样的函数很容易出问题.而我们使用inline函数,加上模板可以更好地实现这种效果.
d)虽然#define可以被这么多种东西替代,但是#define还是必不可少的.#ifndef/#ifdef更是对编译流控制的好方法.
3.尽可能使用const:
a)防止意外修改不该被修改的内容,使之尽早在编译时暴露出来.const可以修饰任何对象,函数参数,函数返回类型,成员函数本体.
b)对于成员函数,如果不希望其改变对象,可以声明为const.
c)const版本和non-const版本的成员函数实现等价时,可以令non-const版本的调用const版本,使用const_cast去除const即可.
4.保证对象在使用前被初始化:
a)在使用对象或者变量前,保证其被初始化,防止出现随机值.
b)对于类的构造函数,最好使用成员初值列表(member initializetion list)来初始化,初值列的成员变量排列次序与声明次序相同(这个不是强制的,但是最好如是执行,比如我们申请一个数组,数组大小应该现有初值才行).而不要在构造函数内进行赋值操作,这样会先默认初始化再赋值,相当于两次操作.
c)跨编译单元的初始化顺序,以local static对象替换non-local-static对象.类似单例模式,我们在使用对象时,有时候不确定初始化的时机,那么我们不如在使用前,将要用的资源一起初始化了.这样就可以控制初始划顺序了.
二.构造,析构,赋值运算
5.了解C++默认编写并调用了哪些函数
a)如果我们什么都不写的话,满足一定条件(c++对象模型-默认构造函数的构造操作),编译器也会为我们生成默认构造函数,析构函数,拷贝构造函数,以及一个赋值运算符,
b)如果我们声明了这些函数,那么C++会使用我们自己编写的这些函数,不再使用哪那些默认的.
c)编译器为我们生成的默认构造函数是没有参数的,如果我们声明了其他的有参数的构造函数,那么这个默认构造函数也会消失,我们如果仍想要无参数的构造函数,那么就要自己再声明一个.
d)编译器为我们生成的析构函数,不会是virtual的,除非这个类的基类有virtual析构函数.
6.若明确不想使用编译器自动生成的函数,就应该明确拒绝
a)有时候我们不容许拷贝或者赋值一个对象时,即使我们不写拷贝或者赋值函数,编译器也会为我们生成一个,怎么办呢>答案是自己写一个private的拷贝构造函数和赋值函数,这样从类外就不能调用了.但是从类内还是可以调用的,这时候,我们可以只声明,不定义,这样,即使有人不小心使用了,在编译链接的时候,也会报错.把错误提前暴露,才容易发现,
b)如果我们怕忘记,或者为了方便,也可以定义一个基类uncopyable,让我们的类继承这个类即可.
7.为多态基类声明虚析构函数
a)如果我们不这样做,当使用基类指针控制子类对象时,当析构时会发生只析构基类部分,而子类部分不被释放的尴尬情况.导致内存泄露.
b)但是这也不代表着为了保险起见,我们全都要用virtual析构函数,因为虚函数的开销是比较大的,所以一般判定需要使用虚析构函数的条件为:只要这个类中有一个其他的虚函数,那么就使用虚析构函数.
c)既然要使用多态的特性,那么如果基类不是虚析构函数,那我们就不要去继承这个类,不然会引出一大串麻烦.比如string,标准stl等都是非析构函数的.
d)析构函数的调用顺序:子类的析构函数先调用,然后依次向上调用父类的析构函数.
8.别让异常逃离析构函数
a)能不抛出异常的,或者感觉会抛出异常的功能,不要放在析构函数中,额外设置一个函数.比如close()关闭函数.但是,在析构函数中仍然要做一下关闭的操作,防止客户忘记调用close发生异常.但是,在析构函数中抛出异常可能导致程序提前结束.应该在析构函数中就catch并处理.
b)被析构函数调用的函数如果抛出异常,析构函数也应该catch掉,保证不要再抛出.
c)如果客户需要对某个操作函数运行期间的异常做出反应,那么最好提供一个普通函数对运行期间抛出的异常做出反应,而不要在析构函数中执行该操作.
9.绝不
a7f9
在构造和析构函数中调用virtual函数
a)我们知道,如果有继承,构造函数会先调用父类的构造函数,而如果构造函数中有虚函数,此时子类还没有构造,所以此时的对象还是父类的,不会触发多态.
b)析构函数也是一样,子类先进行析构,如果析构函数内调用virutal函数的话,子类的内容已经被析构,C++会执行父类的virtual函数,不会出发多态.
c)总之,在构造和析构函数中,不要用虚函数.如果必须用,那么分离出一个Init函数和一个close函数,实现相关功能即可.
10.令operator=返回一个reference to *this
我们经常会为我们的类写一个operator=的函数,为了保证连锁赋值,比如x=y=z的这种情况能正常执行,最好是返回一个*this的引用.
11.在operator=中处理"自我赋值"
a)自我赋值看起来不是那么容易发生,但是程序复杂了什么情况都有,所以为了我们程序的健壮性,还是要处理一下的.
b)自我赋值会导致程序出错的情况在于,如果字段中有指针,先删除指针原来指向的对象,然后将指针指向=右边的对象.而自我赋值时会导致原来的对象被删除,而=右边的对象也被删除,因而会出错.
c)解决的方法其实最简单的就是先判定是否相等,如果和自己是同一个对象,直接返回*this.
12.复制一个对象时勿忘其每一个成分
a)正常情况下,如果我们不写拷贝构造函数,编译器会为我们生成一个默认的拷贝构造函数,但是这个函数只能实现浅拷贝,如果要实现深拷贝就必须要自己写一个拷贝构造函数.
b)但是如果我们自己写了拷贝构造函数,就一定要拷贝每一个成分,如果忘记了某个成分,编译器是不会提醒的.尤其在发生继承的时候,更加需要注意拷贝基类成员部分.通过调用基类函数来进行拷贝.
c)关于initialization和assignment:前者由构造函数执行,后者由operator=执行.两种对应不同的函数动作.两种可能有差不多额内容,但是不要使用一个调用另一个函数,如果实在想要抽象,不如提取一个Init函数.
相关文章推荐
- <<Effective c++>>读书笔记---条款20:宁以pass-by-reference-to-const替换pass-by-value
- <<Effective C++>>读书笔记4: 设计与声明
- <<More Effective C++>>读书笔记6: 杂项
- <<Effective c++>>读书笔记---条款19:设计class犹如设计type
- <<Effective C++>>读书笔记9: 杂项讨论
- <<Effective C++>>读书笔记(三)
- <<More Effective C++>>读书笔记2: 运算符
- <<More Effective C++>>读书笔记3: 异常
- <<Effective C++>>读书笔记(二)
- <<Effective C++>>读书笔记
- <<More Effective C++>>读书笔记4: 效率
- <<Effective C++>>读书笔记8: 定制new和delete
- <<Effective C++>>读书笔记7: 模板与泛型编程
- <<Effective C++>>读书笔记3: 资源管理
- <<Effective C++>>读书笔记(一)
- <<More Effective C++>>读书笔记1: 基础议题
- <Effective C++>读书笔记-6
- <<Effective C++>>读书笔记5: 实现
- <Effective C++>读书笔记-1
- <Effective C++>读书笔记-3