您的位置:首页 > 编程语言 > C语言/C++

<<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函数.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: