Effective C++ 学习笔记(Item7)
item7: declare destructors virtual in polymorphic
想完全弄明白Scott在本章中的建议,需要先了解下虚函数的作用和实际调用机理。明白了这些你才能真正明白为什么作为父类的析构函数必须是虚函数,为什么不作为基类的类里面最好不要用虚函数。
虚函数的编译和运行机制
- 只要类里面有虚函数,或者向上追溯到他的基类有虚函数,那么这个类在实例化的时候就会生成一份虚拟表,用来维护虚拟函数。 编译器会给这个类里面自动添加一个vptr指针,这个指针指向这个虚拟表。
- 当通过指针去调用一个类的接口时,不管这个指针类型跟这个类是否匹配(在多态场景下,可以用父类的指针去调用继承类的接口),如果这个类存在虚拟表,调用过程会先查找虚拟表。而且虚拟表上放的接口一定是跟实际类紧密联系的。
知道上述两点,对虚函数的应用就足够了,至于编译器如何保证虚拟表上的接口一定是跟实际类紧密联系的,那个对实际应用没有啥帮助。
举个例子说明下:
class Base
{
public:
virtual void function1(){cout<<"calling base api"<<endl;};
};
class D1: public Base
{
void function1(){cout<<"calling D1 api"<<endl;};
};
int main()
{
Base aBase;
D1 aD1;
Base* pBase = nullptr;
pBase = &aBase;
pBase->function1();
pBase= &aD1;
pBase->function1();
}
上面的代码因为function1是虚函数,所以虽然都用Base* 类型的指针pBase调用,虚拟表能够保证调用的是真正pBase所指向的实际对象的接口。如果把基类Base中的function1定义的virtual去除,重新编译,你可以观察到在没有virtual的情况下,虽然pBase指向的是不同的对象,但是通过pBase->function1()调用出来的都是基类的function1实现。
上面的代码是有缺陷的,好的IDE会提示你说你的类做了基类,有虚函数,但是析构函数却不是虚函数。
为什么做基类的类要把析构函数定义成虚函数
因为多态,可能会用父类指针指向一个之类。如果想销毁该之类的时候,也用的是父类指针去触发销毁,那么当父类的析构函数不是虚函数的时候,销毁时就只会调用父类的析构函数,而到指定之类成分被意外的没有释放。
为什么不做基类的类最好不要把析构函数定义成虚函数
如果某个类不做基类,那么就没有多态波及的虚函数问题。那么这时候定义虚函数只会让这个类的体积变大。因为编译器需要添加一个指针指向虚拟表,同时每次调用过程都要检索一次虚拟表。运行效率也会地下。所以不做基类的class就不要定义虚函数了。
不做基类的函数被其他人意外继承了怎么办
设计者认定某个类不做基类,那么就不会设计虚函数进去,但是其他开发者万一意外的继承了这个类就会导致问题。 这里需要强调的是STL的string类和容器类都是不希望你去继承的,设计的时候并没有使用virtual 析构函数。遗憾的是c++没有类似final(java)的防止继承限定,只能靠开发者自己去避免误使用。
巧用纯虚析构函数
当你要设计一个抽象类,但是类里面没有一个纯虚函数的,可以使用纯虚的析构函数,但是为了编译错误,还要给纯虚析构函数定义实现体。这种技巧就实际工作而言,没有用到过。感觉很鸡肋的技巧。
网友评论