编译器自动生成的函数包括数据库
1. 构造函数(default)小程序
2. 拷贝构造函数(copy)数组
3. 析构函数ide
4. 拷贝复制运算符(copy assignment)函数
若是一个class的成员变量含有reference(引用)类型,那么必须本身定义copy assignment操做符。spa
编译器自动构造的copy assignment的默认操做,是逐一调用成员变量的copy assignment,可是reference没有copy assignment,也不容许改变。所以,若是不本身定义copy assignment,编译器就会报错。3d
和reference同样一旦赋值就不可更改的const类型也同样,只要class的成员变量含有reference和const类型,就必须自定义copy assignment操做符。指针
编译器自动生成的函数包括orm
1. 构造函数(default)xml
2. 拷贝构造函数(copy)
3. 析构函数
4. 拷贝复制运算符(copy assignment)
若是不想让编译器,自动生成上述函数,而且禁止调用这些函数,该将该函数声明为private,而且没必要实现。
一般,禁止的函数为2.拷贝构造函数和4.拷贝赋值运算符
这里给多态基类特殊标记,由于不是全部的基类都须要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;
能够发现,程序并无调用子类AtomicClock的析构函数,这就致使AtmoicClock对象并无被销毁,而其父类TimeKeeper对象却被正常销毁,形成一种“局部销毁”,造成所谓的内存泄露。
如今为TimerKeeper的析构函数加上关键字“virtual”
class TimeKeeper{ public: TimeKeeper(void){} virtual ~TimeKeeper(void){ std::cout << "delete TimeKeeper" << std::endl; } };
能够看到,子类AtomicClock的析构函数也被正常调用,这样才是完成的对象销毁。
2、不要继承没有virtual析构函数的class,尤为是标准库的类。
3、若是一个class,不被任何类继承,不要将其析构函数设为virtual
有时,为了方便,无论该类是否是基类(base class),都将其析构函数设为virtual。可是,设为virtual结构是有代价的。
class Point{ public: Point(int x, int y); ~Point(); private: int x, y; };
若是int是32bits,那一个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++的关键字,既然使用它,还谈什么和传递给C,C原本就不认识virtual。
4、若是class没有任何一个纯虚函数,却又想让该class为abstract 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;
若是仅仅是这样,运行程序,你会发现,程序报错
这里就涉及到析构函数的运行方式,最深层派生(most derived)的class的析构函数最早被调用,而后是每个base class的析构函数被调用。在本例中,当ddelete a时,Child的析构函数被调用,而后是AWOV的析构函数,可是AWOV的纯虚析构函数并无实现,所以形成连接错误。
所以,就算析构函数被定义为pure virtual,它也必定要有实现!从资源释放的角度来看,析构函数是释放资源和内存的关键,所以必需要实现它。
PS:为何普通的方法声明为pure virtual后,就不要实现了呢?
1. 不须要实现
2. 实现也没用
普通的方法声明为pure virtual,就是但愿有子类区重载该方法,若是子类不重载该pure virtual方法,那子类也是抽象类,没法被实例化。所以,必须有子类重载该pure virtual方法,一旦该pure virtual方法被重载,那它的实现根本不会被调用!
PS:构造函数必定不能是virtual,由于用到虚函数,就要使用虚函数表(Virtual Table)。但此时,构造函数并没执行,当前对象还没构造出来,所以虚函数表更是不存在。
这条Tips的含义就是,不要在析构函数中抛出异常。释放对象的过程会调用析构函数(这点没什么好说的),若是连续释放多个对象,而又有不止一个对象的析构函数抛出异常,就会致使程序中出现多个异常。事实上,在两个异常的存在下,C++程序不是结束执行就是产生不明确结果。
1、对会抛出异常的操做,class应提供普通的方法,这样客户才能有机会处理异常。
对于某些操做,当程序执行时,必须调用这些操做。例如,关闭数据库的链接。通常为了防止客户忘了关闭链接,会将关闭操做放在析构函数中。根据Tips的含义,若是发生异常,析构函数只能使程序关闭,或者吞掉该异常,让程序继续执行。但这两种方法,都会使客户没法对异常进行处理/响应
所以,惟一可行的办法,就是让客户本身处理异常,一旦程序由于异常而不能正常运行,客户就应该知道本身处理异常。因此,class应该为会抛出异常的操做提供方法。
二、析构函数对于会抛出异常操做的处理方式。
有两种处理方式
① 遇到异常就结束程序,用过abort完成。
try{ }catch (...){ std::abort(); }
② 遇到异常就吞下
try{ }catch (...){ 制做运转记录,记下对close的调用失败 }