0%

Effective C++笔记(二)

条款05:了解C++默默编写并调用了哪些函数

当声明一个“空”类的时候,编译器会默默为它声明一个Default构造函数、copy构造函数,copy assignment操作符和一个析构函数;所有这些函数都是public且inline的。

  • Default构造函数和析构函数用来放置藏身幕后的代码,比如调用base classes和non-static成员变量的构造和析构函数;
  • copy构造函数和copy assignment操作符,编译器生产的版本只是单纯第将源对象的non-static的成员变量拷贝到目标对象;
  • 如果打算在一个内含reference成员的class内支持赋值操作,必须自己定义copy assignment操作符;
  • 对内含const成员的类,同样需要自己定义;

条款06:若不想使用编译器自动生产的函数,就应该明确拒绝

条款5已经知道,我们不需要自己动手编写拷贝构造函数和拷贝赋值操作符,借助编译器自动生成默认的版本,就可以实现类对象的拷贝和赋值。在某些情况下这是非常方便的,但假设我们禁止对象之间的拷贝和赋值,但是又无法阻止编译器隐式自动生成默认的拷贝构造和拷贝赋值操作符,那该怎么办呢?

在C++11中比较简单,使用delete就可以禁用掉copy构造函数和copy assignment操作符,在函数第一次声明的时候将其声明为deleted函数:

1
2
3
4
class Example {
Example(const Example&) = delete;
Example& operator=(const Example&) = delete;
};

而对于C++11以前的版本,可以将相应的成员函数声明为private并且不予实现。或者设计一个阻止拷贝的通用基类,将copy构造函数和copy assignment操作符声明为private,让禁止对象拷贝和赋值的类继承该通用基类。背后的原理是,当编译器试图为子类生成copy构造函数和copy assignment操作符时,会调用基类的对应函数,编译器会拒绝因为基类的对应函数是private的。

条款07:为多态基类声明virtual析构函数

在C++种,返回实际指向派生类对象的基类类型指针是非常常见的操作,这也是实现多态的基础。这种情况下,需要通过delete基类指针来释放子类对象资源;如果此时基类有个non-virtual的析构函数,就会引来灾难。

C++明确指出,当派生类对象经由一个基类指针被删除,而该基类带有一个non-virtual析构函数,其结果是未定义的——通常发生的情况是对象的派生成分没有被销毁,而其积累成分被销毁了,造成一个诡异的局部销毁对象。一个深坑就此挖好了。。。

解决办法很简单:给基类声明一个virtual析构函数。如果某个类不包含virtual函数,通常表示它并不计划被作为一个基类,当类不被作为基类,令其析构函数为virtual也不是个好主意。因为一旦声明了virtual函数,就会有一个虚表指针指向一个由函数指针构成的数组,导致对象体积增加。因此,无端地将所有类的析构函数声明为virtual,和从未声明它们为virtual一样,都是糟糕的做法。

记住:

  • 带多态性质的基类应该声明virtual析构函数;如果类带有任何virtual函数,它就应该有一个virtual析构函数;
  • 类的设计目的不是作为基类使用,或不是具备多态性质,就不应该声明virtual析构函数;
  • 不要企图继承一个标准容器或者其他任何带有non-virtual析构函数的类。

条款08:别让异常逃离析构函数

  • 析构函数绝对不要抛出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下或者结束程序;
  • 如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么类应该提供一个普通函数而非在析构函数中执行该操作。