《Effective C++》 笔记:Tips05-Tips08

Tips05:了解C++悄悄编写并调用哪些函数

编译器自动生成的函数包括数据库



  1. 1. 构造函数(default)小程序

  2. 2. 拷贝构造函数(copy)数组

  3. 3. 析构函数ide

  4. 4. 拷贝复制运算符(copy assignment)函数



若是一个class的成员变量含有reference(引用)类型,那么必须本身定义copy assignment操做符。spa



编译器自动构造的copy assignment的默认操做,是逐一调用成员变量的copy assignment,可是reference没有copy assignment,也不容许改变。所以,若是不本身定义copy assignment,编译器就会报错。3d



reference同样一旦赋值就不可更改的const类型也同样,只要class的成员变量含有referenceconst类型,就必须自定义copy assignment操做符。指针





Tips06:若不想使用编译器自动生成的函数,就该明确拒绝

编译器自动生成的函数包括orm



  1. 1. 构造函数(default)xml

  2. 2. 拷贝构造函数(copy)

  3. 3. 析构函数

  4. 4. 拷贝复制运算符(copy assignment)



若是不想让编译器,自动生成上述函数,而且禁止调用这些函数,该将该函数声明为private,而且没必要实现。



一般,禁止的函数为2.拷贝构造函数和4.拷贝赋值运算符




Tips07:为多态基类声明virtual析构函数

这里给多态基类特殊标记,由于不是全部的基类都须要virtual析构函数的,正由于用到了多态,才有了virtual析构函数的必要。





1、为何要为多态基类声明virtual析构函数。



先来看一段简单的小程序



class TimeKeeper{           
public:          
       TimeKeeper(void){}           
       ~TimeKeeper(void){           
              std::cout << "delete TimeKeeper" << std::endl;           
       }           
};

   

class AtomicClock : public TimeKeeper{
public:
       ~AtomicClock(void){
              std::cout << "delete AtomicClock" << std::endl;
       }
};



客户端代码:    

TimeKeeper *timer = new AtomicClock();
delete timer;



p_w_picpath003

能够发现,程序并无调用子类AtomicClock的析构函数,这就致使AtmoicClock对象并无被销毁,而其父类TimeKeeper对象却被正常销毁,形成一种“局部销毁”,造成所谓的内存泄露。



如今为TimerKeeper的析构函数加上关键字“virtual

class TimeKeeper{       
public:      
       TimeKeeper(void){}       
       virtual ~TimeKeeper(void){       
              std::cout << "delete TimeKeeper" << std::endl;       
       }       
};

 





p_w_picpath006

能够看到,子类AtomicClock的析构函数也被正常调用,这样才是完成的对象销毁。





事实上,任何带有virtual方法的class,都须要virtual析构函数。很简单,既然class带有virtual方法,其目的就是想让其子类重载该virtual方法,也就说明该class是多态基类,既然是多态基类,只有带有virtual析构函数,才不会出现“局部销毁”。



2、不要继承没有virtual析构函数class,尤为是标准库的类。



3、若是一个class,不被任何类继承,不要将其析构函数设为virtual      



有时,为了方便,无论该类是否是基类(base class),都将其析构函数设为virtual。可是,设为virtual结构是有代价的。

class Point{
public:
       Point(int x, int y);
       ~Point();
private:
       int x, y;
};

若是int32bits,那一个Point对象就是64bits。若是Point的析构函数virtual,那么Point的大小就不止64bits

对象须要携带额外的信息,来决定运行期间到底运行那个virtual函数,而决定权是在一个叫vptr(虚函数表指针)的手上,vptr指向一个由函数指针构成的数组——vtbl(虚函数表)。每一个带有virtual函数的class都有一个vtbl,例如virtual析构函数Point。当对象调用某一virtual函数时,实际被调用的是vptr指向的那个vtbl

所以,在32位机上,2*32bits(int)+32bits(vptr) = 96bits。不管何种类型的指针,大小均为32bits

64位机上,2*64bits(int)+64bits(vptr) = 128bits。同理在64位机上,指针为64bits

这就致使C++对象再也不和其余语言(C)有着相同的结构,也就再也不可能把他传递(或接受)其余语言缩写的函数。

总的来讲,就是丧失的移植性

PS:其实我一直不太理解,书上这段话的含义,virtual原本就是C++的关键字,既然使用它,还谈什么和传递给CC原本就不认识virtual






4、若是class没有任何一个纯虚函数,却又想让该classabstract class

想让class成为abstract class(抽象类)的缘由是,不想让该class被实例化,既不能new。但该类的全部普通方法均要实现,这又不构成抽象类的条件。



方法就是,将析构函数设置为纯虚函数(pure virtual)


class AWOV{
public:
       AWOV(void);
       virtual ~AWOV(void) = 0;
};
                     
class Child : public AWOV{
public:
       Child(void);
       ~Child(void);
};



客户端代码:


AWOV *a = new Child();
delete a;

若是仅仅是这样,运行程序,你会发现,程序报错



p_w_picpath009

这里就涉及到析构函数的运行方式,深层派生(most derived)class的析构函数最早被调用,而后是每个base class的析构函数被调用。在本例中,当ddelete a时,Child的析构函数被调用,而后是AWOV的析构函数,可是AWOV纯虚析构函数并无实现,所以形成连接错误。



所以,就算析构函数被定义为pure virtual,它也必定要有实现!从资源释放的角度来看,析构函数是释放资源和内存的关键,所以必需要实现它。



PS:为何普通的方法声明为pure virtual后,就不要实现了呢?



  1. 1. 不须要实现



  1. 2. 实现也没用



普通的方法声明为pure virtual,就是但愿有子类区重载该方法,若是子类重载该pure virtual方法,那子类也是抽象类,没法被实例化。所以,必须有子类重载该pure virtual方法,一旦该pure virtual方法被重载,那它的实现根本不会被调用!


PS:构造函数必定不能是virtual,由于用到虚函数,就要使用虚函数表(Virtual Table)。但此时,构造函数并没执行,当前对象还没构造出来,所以虚函数表更是不存在。


Tips08:别让异常逃离析构函数

这条Tips的含义就是,不要在析构函数中抛出异常。释放对象的过程会调用析构函数(这点没什么好说的),若是连续释放多个对象,而又有不止一个对象的析构函数抛出异常,就会致使程序中出现多个异常。事实上,在两个异常的存在下,C++程序不是结束执行就是产生不明确结果。



1、对会抛出异常的操做,class应提供普通的方法,这样客户才能有机会处理异常。



对于某些操做,当程序执行时,必须调用这些操做。例如,关闭数据库的链接。通常为了防止客户忘了关闭链接,会将关闭操做放在析构函数中。根据Tips的含义,若是发生异常,析构函数只能使程序关闭,或者吞掉该异常,让程序继续执行。但这两种方法,都会使客户没法对异常进行处理/响应



所以,惟一可行的办法,就是让客户本身处理异常,一旦程序由于异常而不能正常运行,客户就应该知道本身处理异常。因此,class应该为会抛出异常的操做提供方法。





析构函数对于会抛出异常操做的处理方式。



有两种处理方式



  遇到异常就结束程序,用过abort完成。

try{
}catch (...){       
       std::abort();       
}


  遇到异常就吞下

try{
}catch (...){       
             制做运转记录,记下对close的调用失败
}
相关文章
相关标签/搜索