了解C++默默编写并调用哪些函数

  1. 如果没有声明任何构造函数,编译器会声明一个默认构造函数,另外如果我们没声明拷贝控制成员的话,编译器会声明一个拷贝构造函数、拷贝赋值函数和一个析构函数。只有当这些函数被需要(被调用),它们才会被编译器创建出来。
  2. 编译器创建的析构函数是非虚函数,除非这个类的基类自身声明有虚析构函数。
  3. 只有当生出的代码合法且有适当机会证明它有意义,编译器才会生成对应的拷贝构造函数和拷贝赋值函数,比如将引用重新赋值这就属于不合法操作,不会生成构造赋值函数。如果想让这个class支持赋值运算符就必须自己定义拷贝赋值函数。
  4. 如果某个基类将拷贝赋值操作符声明为private,编译器将拒绝为其派生类生成拷贝赋值运算符,因为编译器为派生类所生成的拷贝赋值运算符想象中可以处理基类部分,但它们无法调用基类中的这一成员函数因此编译器无能为力。
  5. 为了避免编译器自动生成上述函数,可以将相应的成员函数声明为private并且不予实现。使用基类将这些成员函数声明为private,然后再让其他类继承自这个类也能避免这一问题。

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

  1. 当派生类对象经由一个基类指针被删除,而该基类带着一个非虚的析构函数,其结果未有定义,通常是对象的派生类部分未被销毁,形成资源泄露。消除这个问题只需要给基类一个虚析构函数。
  2. 当一个类不企图当作基类时,将其虚构函数声明为虚函数是个不好的主意,因为实现该虚函数,对象通常需要携带虚函数表指针,vptr指向一个由函数指针构成的数组,称为vtbl,每一个带有虚函数的class都有一个对应的vtbl。当对象调用某一虚函数,实际被调用的函数取决于该对象的vptr指向的那个vtbl——编译器在其寻找适当的函数指针。
  3. class的设计目的如果不是作为基类使用或者不是为了具备多态性,就不该声明虚析构函数以免造成更大的开销。
  4. 含有纯虚函数的类被称为抽象类,抽象类无法被实例化。

别让异常逃离析构函数

  1. 析构函数绝对不要抛出异常,如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吐下它们(不传播)或结束程序。
  2. 如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。

绝不在构造和析构过程中调用虚函数

  1. 在基类对象构造期间,虚函数不是虚函数,只会执行当前基类中的虚函数。如果此时调用的是派生类中虚函数,而派生类中的成员变量尚未初始化,我们不能使用对象内部未初始化的成分,并且在基类构造期间对象类型一直都是基类类型,对象在派生类构造函数开始前不会成为一个派生类对象。
  2. 一旦派生类对象析构函数开始执行,对象内的派生类成员变量便呈现未定义值,进入基类对象析构函数后对象就变为一个基类对象。
  3. 派生类构造过程中避免调用基类版本的虚函数的唯一做法就是确定构造函数和析构函数都没有(在对象被创建和被销毁期间)调用virtual函数,而它们所调用的所有函数也都符合这一约束。
  4. 由于无法使用virtual函数从基类向下调用,在构造期间,可以藉由“令派生类将必要的构造信息向上传递至基类构造函数”替换之加以弥补。

令operator=返回一个reference to *this

  1. 为了实现“连锁赋值”,赋值操作符必须返回一个reference指向操作符的左侧实参,这个协议不仅适用于标准赋值形式,也适用于所有赋值相关运算。这只是个协议,并无强制性。
    int x,y,z;
    x=y=z=15;//赋值连锁形式
    x=(y=(z=15));//赋值采用右结合律
    

在operator中处理“自我赋值”

  1. “别名”就是“有一个以上的方法指称某对象”,如果某段代码操作pointers和references而它们被用来“指向多个相同类型的对象”,就需考虑这些对象是否为同一个。两个对象只要来自同一个继承体系,他们甚至不需声明为相同类型就可能造成“别名”,因为一个基类的引用或指针可以指向一个派生类对象。
  2. 欲阻止自我赋值操作中可能将本身的某个资源先删除再将此删除资源赋值的错误,传统做法是可以在operator=函数里最前面加上一个“认同测试”达到“自我赋值”的校验目的。但是仍然有可能在分配内存或其他地方发生异常。
  3. 具备“异常安全性”往往具有“自我赋值安全性”,因此为了实现“异常安全性”,我们需要在复制本身的资源前别将其删除这样就算发生异常也不会对其本身有害,只不过这样效率不是最高。
  4. “自我赋值”效率较高的解决方法是使用copy and swap技术,先为实参本身创建一个副本然后再调用自己定义的swap函数,将本身与实参副本数据进行交换。
  5. 确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。

复制对象时勿忘其每一个成分

  1. 如果我们为class添加一个成员变量且自己定义拷贝构造函数和拷贝赋值运算符,我们必须同时修改两者(也需要修改class的所有构造函数以及任何非标准形式的operator=),如果我们忘记了,编译器将不会进行提醒。
  2. 派生类拷贝构造成员如果只拷贝了另一个派生类对象成员,而派生类中还包含者基类成员,这些基类成员却未被复制。如果派生类的拷贝构造函数未将指定实参传递给基类构造函数,那么派生类对象中的基类部分将被不带实参的基类默认构造函数初始化。
  3. 当我们为派生类定义拷贝控制成员时,我们必须小心地复制其基类部分,那些成分往往是private的,派生类无法直接访问,因此需要让派生类的拷贝控制成员调用相应的基类中的拷贝控制成员函数。
  4. 复制对象的每一个部分不仅包括自身的local成员变量,还包括调用所有基类中的适当的拷贝控制成员。
  5. 不要尝试以某个拷贝控制成员实现另外一个拷贝控制成员,应该将共同机能放进第三个函数中,并由这两个函数共同调用。