条款05:了解C++默认编写并调用哪些函数
对于一个空类,编译器会自动为此类生成默认构造函数,拷贝构造函数,析构函数,和拷贝赋值运算符。
例如,如果你的类仅有class Empty{};
但实际上却是:
class Empty{
public:
Empty(){......}
Empty(const Empty &rhs) {......}
~Empty() {......}
Empty& operator=(const Empty& rhs) {......}
};
以上函数被需要的时候就会被编译器创建出来。如果我们声明了其中的函数,编译器将不再为我们创建默认函数。
更精确的描述是:如果默认产生的函数语法正确,并且被需要,则编译器就会产生默认函数,否则编译器拒绝产生。例如类成员变量中有引用或者常量,由于C++语法不允许引用改变所指对象,所以编译器一定会拒绝默认拷贝赋值运算符。还有一种情况是某个基类将拷贝构造运算符声明为private,则编译器拒绝为其派生类生成一个赋值拷贝运算符。
=================================================================================================================
条款06:若不想使用编译器自动生成的函数,必须明确拒绝
可以把拒绝的函数声明为private,此时即绝了编译器的自动生成,有成功阻止了人们的调用。一般而言这种做法也不是绝对安全,因为成员函数或则友元函数还是可以访问private的,除非你不是定义他们。也可以专门构造一个基类去阻止函数自动生成,然后再去继承他,此时成员函数和友元函数也是无法访问的。
==================================================================================================================
条款07:为多态基类声明virtual析构函数
为何多态基类的析构函数要为虚函数:因为C++明确指出,当派生类对象经由一个基类指针被删除,而该基类的析构函数是非虚的,其结果是未定义的——实际上执行时通常发生的是对象的派生类对象部分没被销毁。
如果class不含虚函数,通常表示他并不意图被用作基类。此时把析构函数声明为虚函数是个馊主意。
一般只要class中至少有一个虚函数,才为他的析构函数声明为虚函数。
即使class完全不带虚函数,也有可能因为非虚析构函数问题造成资源泄露。如继承自一个标准容器或其他带有非虚析构函数的Class。
=====================================================================================================================
条款08:别让异常逃离析构函数
C++不喜欢析构函数突出异常。
如果你的析构函数必须执行一个动作,而该动作可能会失败抛出异常怎么办?
两种方法可以禁止异常离开析构:(1)在发生异常时强制结束程序;(2)记录异常然后跳过;
然而这两种做法都不太好,一个较佳的策略是重新设计类的接口,使用户有机会对可能出现的问题作出反应。即把可能发生异常的部分做成类的成员函数,并添加一个开关(布尔值),然后在析构函数中调用此函数(双保险)。
===================================================================================================================
条款09:绝不在构造函数和析构函数中调用virtual函数
原因:基类构造期间virtual函数绝对不会下降的派生类阶层。取而代之的是,对象的作为就行隶属base类型一样。非正式的说法:在基类构造期间,虚函数不是虚函数。
对象在派生类构造函数开始之前不会成为一个派生类。
相同的道理也适用析构函数。
由于我们无法使用虚函数从基类向下调用,自构造函数期间,我们可以藉由“令派生类将必要的构造信息向上传递至基类的构造函数”替换加以弥补。
===================================================================================================================
条款10:令operator=返回一个reference to *this
为了实现“连续赋值”,赋值操作符必须返回一个reference指向操作符的左侧实参。(为了保持队形)
===================================================================================================================
条款11:在operator=中处理“自我赋值”
一般自我赋值是因为“别名”:所谓别名就是指有一种以上的方法指向某个对象。
阻止自我赋值发生错误的方法:
(1)“证同测试”
(2)删除之前先复制
(3)使用copy and swap技术:
例:widget& widget::operator=(const widget& rhs)
{
widget temp(rhs);
swap(temp);
return this;
}
====================================================================================================================
条款12:复制对象时勿忘其每一个成分
不要忘记基类的成员函数。
下面是一个注意事项:令拷贝赋值操作符调用拷贝构造函数是不合理的,反过来同样不合理。如果两者真的很相近,我们可以创建一个新的成员函数给他们调用。为何不合理,原因是:构造函数调用拷贝赋值操作符,就像一个尚未初始化的对象做了“只对已初始化对象才有意义”的时。反过来则是试图再次构造一个已经存在的对象。