构造/析构/赋值运算
条款5:了解C++默默编写并调用了哪些函数
如果自己没有声明,编译器会为类声明一个默认构造函数、拷贝构造函数、赋值操作函数、析构函数。这些函数都是public且inline的。
只有这些函数被调用时,才会被编译器创建出来。
默认构造函数和析构函数是用来调用基类和非静态成员的构造函数和析构函数。
析构函数是非virtual的,除非该类的基类自身声明了virtual析构函数。此时析构函数的virtual性来自其基类。
拷贝构造函数和赋值操作只是单纯的将源对象的非static成员拷贝到目标对象。成员有拷贝构造函数则调用其拷贝构造函数并以成员为参数完成。内置类型则拷贝每个bit完成。
如果在内含引用成员或const成员的类中支持赋值操作,必须自定义赋值操作符。
如果基类将赋值操作声明为private,编译器拒绝为其派生类生成赋值操作函数,因为派生类的赋值操作要处理基类成分,但却无法调用基类的private函数。
条款6:若不想使用编译器自动生成的函数,就明确拒绝
为驳回编译器自动提供的机能,可以将相应的成员函数声明为private并且不予实现。
或使用基类形式,将基类的拷贝构造函数和赋值操作声明为private,派生类就不可拷贝和赋值了。
条款7:为多态基类声明virtual析构函数
一个基类指针指向一个派生类对象,如果基类析构函数非virtual,析构时派生成分没有被销毁,基类成分被销毁,也就是局部销毁对象,资源泄露。
解决:给多态基类一个virtual析构函数。任何带有virtual函数的类都需要virtual析构函数。
如果一个类不含virtual函数,通常表示不希望被用来做基类,此时不要令其析构函数为virtual。含有virtual函数,对象体积会增加,因为有虚表指针。
只有当类含有virtual函数时才为它声明virtual析构函数。
不要试图继承一个标准容器或任何带非virtual析构函数的类!
为声明一个抽象类但没有其他纯虚函数时可把析构函数声明为纯虚函数,但必须为他提供一个定义。
多态基类的设计目的就是为了通过基类接口处理派生类对象,并非所有基类的设计目的都是为了多态用途。多态基类应该声明virtual析构函数。如果类带有任何virtual函数,它就应该拥有virtual析构函数。若类的设计目的不是做为基类使用或是不具备多态性,就不应该声明virtual析构函数。
条款8:别让异常逃离析构函数
C++不喜欢析构函数吐出异常,也就是不希望它传递异常。
析构函数吐出异常,程序有可能提前结束或是出现不明确行为。
两种方式不让析构函数传播异常:
1、析构函数中抛出异常,结束程序,通常调用abort函数完成。
2、吞下异常,一般不好,但有时候可能比草率结束好。
最好的策略是重新设计接口,是其客户有机会对可能出现的问题作出反应,给客户一个处理错误的机会
析构函数绝对不要吐出异常,如果一个函数被析构函数调用且可能抛出异常,析构函数应该捕获并吞下它们或结束程序。
如果客户需要对异常作出反应,类应该提供一个普通函数执行该操作,而不是在析构函数中。
条款9:绝不在构造函数和析构函数中调用virtual函数
派生类对象构造时,基类构造函数先被调用来构造基类成分。基类构造函数调用virtual函数是基类的版本而不是派生类的版本。即当前建立的对象类型是基类类型。基类构造期间virtual函数绝不会下降到派生类阶层,而是对象行为就像基类类型一样。在基类构造期间,virtual函数不是virtual函数。
基类构造函数执行时,派生类成分尚未初始化。派生类对象的基类构造期间,对象类型是基类而不是派生类。
确定构造和析构函数都没有调用virtual函数,而它们调用的所有函数也都服从同一约束。
替代方案:要求派生类构造函数传递必要信息给基类构造函数。可以利用static函数创建值传递给基类构造函数。
条款10:令operator=返回一个*this引用,可以实现链式,连锁赋值。
条款11:在operator=中处理自我赋值
传统做法是增加证同测试,如果自我赋值,就不做任何操作。
异常安全性往往获得自我赋值安全。
copy-and-swap
确保对象自我赋值行为良好,技术包括比较源和目的对象的地址、精心安排语句顺序和copy-and-swap
条款12:复制对象时勿忘其每个成分(包括拷贝构造和赋值操作)
拷贝函数应确保复制对象内所有成员变量及其基类所有成分。
不要尝试以某个拷贝函数实现另一个拷贝函数,应该将共同机能放进第三个函数中,并由两个拷贝函数共同调用。