条款11:要在类内声明一个copy constructor和一个assignment运算符
解释:如图,如果没有自定义的拷贝构造函数,当执行赋值时会出现至少两个问题:第一,b原先所指的内存没有释放掉,会永远遗失,出现内存泄漏问题;第二,a和b内含的指针指向相同的字符串,当其中一个离开生存空间,析构函数会删除内存,而此内存目前仍被另一个指针所指。
条款12:在constructor中尽量以initialization动作取代assignment动作
解释:撰写构造器时,要将参数值传给类的数据成员,有两种做法:
因为const成员和reference成员不能被赋值,只能初始化,这时候必须使用初始化列表;如果类中没有const或reference成员,即便如此,初始化列表效率更高,因为使用初始化列表,只会调用一次成员函数,而使用assignment动作,会调用两次成员函数,当没有为name设置一个初值时,会调用默认的构造函数,稍后在NamedPtr构造器内为name执行assignment动作时,会在name身上执行operator=函数,因此调用了两次,初始化列表方式为name设定初值,只会调用一次拷贝构造函数。
有一种情况以assignment代替初始化是合理的。那是当有一大堆私有数据成员,而希望在每个构造器中以相同的方式将它们初始化的情况。
因为对于非常量和非引用的对象,初始化和assignment之间没有任何操作上的差异,可以使用一个函数调用动作取代上述例子的初始化,可替代为:
条款13:initialization list的成员初始化次序应该和其类内的声明次序相同
解释:类中的成员是以类内的声明次序初始化的,和其成员初始化列表中的次序完全无关。为了避免编译器每次跟踪每个对象的成员初始化次序,要保证任何类型的所有对象,其构造和析构次序都相同。
条款14:基类的析构函数要定义为virtual
要注意类的静态成员要定义在类外,如下:
当定义一个虚函数时,编译器会产生一个vptr指向一个由函数指针形成的数组vtbl,每个带有虚拟函数的类都有一个响应的vtbl,当程序在某个对象身上调用虚拟函数时,编译器会循着该对象的vptr所指的vtbl,决定实际调用哪个函数。
定义纯虚函数时,要为其提供一个定义式。如下:
class AWOV{
public:
virtual ~AWOV()=0;
};
AWOV::~AWOV(){}
因为派生程度最深的类的析构函数先被调用,然后调用基类的析构函数,所以编译器会产生一个基类析构函数的调用动作,所以要有一个函数实体。
条款15:operator=传回*this的引用
为支持上述这种情况,operator=的返回型别必须能被它自己这种函数接受,作为一个输入。
条款16:在operator=中为所有的数据成员设定赋值内容
没有派生类时直接赋值,如下:
有派生类时如下:
将上述错误的拷贝构造函数改为下面的
因为下面的这种拷贝构造函数保证派生类的拷贝构造函数调用基类的拷贝构造函数,而不是基类的默认构造函数,这样当派生类的一个对象赋值给另一个对象时,基类的私有成员也会改变。
条款17:在operator=中检查是否自己赋值给自己
使用对象等同或者数值等同或者地址等同判断,如下: