虚析构函数与多态
2015-10-10 23:46
246 查看
多态基类的析构函数应该为虚函数
结果:
然而,如果将基类析构函数设为非虚,其他不变,得到的结果将是:
当使用
产生这个问题的原因是:当一个派生类对象通过一个基类指针删除,并且这个基类的析构函数是非虚的,c++将不会调用整个析构函数链,结果是未定义的。所以这种情况下,只调用了基类CBird的析构函数,对象的派生部分并没有被销毁。
为了改变这个,就出现了上述虚析构函数virtual ~Base()。
只要将基类的析构函数设为virtual型,则所有派生类都自动将析构函数设为virtual型,这样子就保证了不会因为析构函数未被调用而导致内存泄露。
并不是所有c++类都应该将析构函数设置为virtual。只有具有virtual函数的多态基类(或者其它想当base class的类)才应该将析构函数设置为virtual,对于普通的类则无必要。因为虚函数的实现要求对象携带额外信息,也就是维护一个指向虚函数表的指针vptr(virtual table pointer),vptr指向虚函数表vtbl(virtual table)。当调用一个对象的虚函数时,就会通过vptr找到vtbl,在vtbl中寻找正确的函数指针调用。由于vptr的加入,导致对象大小增加。所以对于非多态基类,没必要将析构函数声明为virtual以带来额外负担。这同时引出另外一条准则,如果一个类的析构函数非虚,那就说明它不想当爹,程序员要顶住诱惑,拒绝继承它,即使它“出身名门”,比如标准库中的string等等。
另外,有时还会将析构函数设置为纯虚函数(pure virtual),拥有纯虚函数的类变为抽象基类(abstract class),抽象基类不能被实例化。如果某个class只希望作为base class(不希望被实例化),但是又没有一个纯虚函数,而base class应该有一个virtual析构函数,那么此时就可以将析构函数设置为纯虚函数。
必须为纯虚析构函数提供定义,否则会出现Link错误。因为析构函数的运作方式是,最深层派生(most derived)的那个class其析构函数最先被调用。然后是其每一个base class的析构函数被调用。
下面再给出一个例子:
结果:
#include <iostream> using namespace std; class Base { public: Base() { cout<<"Base()"<<endl; } virtual ~Base()//多态基类的析构函数应该为虚函数!!!!!! { cout<<"~Base()"<<endl; } }; class Derived:public Base { public: Derived() { cout<<"Derived()"<<endl; } ~Derived() { cout<<"~Derived()"<<endl; } }; int main() { Base *pBase; cout<<"1"<<endl;//便于查看哪一步进行了什么操作 Derived derived;//构造,先基类后子类 cout<<"2"<<endl; pBase = &derived; cout<<"3"<<endl; delete pBase;//析构,先子类后基类 cout<<"4"<<endl; //析构 return 0; }
结果:
1 Base() Derived() 2 3 ~Derived() ~Base() 4 ~Derived() ~Base() Process returned 0 (0x0) execution time : 1.224 s Press any key to continue.
然而,如果将基类析构函数设为非虚,其他不变,得到的结果将是:
1 Base() Derived() 2 3 ~Base() //可以看到,未调用~Derived()!!!! 4 ~Derived() ~Base() Process returned 0 (0x0) execution time : 0.625 s Press any key to continue.
当使用
delete pBase时,释放pBase所指的内存,而pBase所指的是Derived对象,为什么不会调用Derived的析构函数呢?这里由于是静态联编,pBase在编译时已经确定所指向的函数是Base的函数,而不会根据对象derived来确定指向的函数。如此一来如果Derived申请了内存资源,但是没有在它的析构函数中释放资源,造成内存泄露。
产生这个问题的原因是:当一个派生类对象通过一个基类指针删除,并且这个基类的析构函数是非虚的,c++将不会调用整个析构函数链,结果是未定义的。所以这种情况下,只调用了基类CBird的析构函数,对象的派生部分并没有被销毁。
为了改变这个,就出现了上述虚析构函数virtual ~Base()。
只要将基类的析构函数设为virtual型,则所有派生类都自动将析构函数设为virtual型,这样子就保证了不会因为析构函数未被调用而导致内存泄露。
并不是所有c++类都应该将析构函数设置为virtual。只有具有virtual函数的多态基类(或者其它想当base class的类)才应该将析构函数设置为virtual,对于普通的类则无必要。因为虚函数的实现要求对象携带额外信息,也就是维护一个指向虚函数表的指针vptr(virtual table pointer),vptr指向虚函数表vtbl(virtual table)。当调用一个对象的虚函数时,就会通过vptr找到vtbl,在vtbl中寻找正确的函数指针调用。由于vptr的加入,导致对象大小增加。所以对于非多态基类,没必要将析构函数声明为virtual以带来额外负担。这同时引出另外一条准则,如果一个类的析构函数非虚,那就说明它不想当爹,程序员要顶住诱惑,拒绝继承它,即使它“出身名门”,比如标准库中的string等等。
另外,有时还会将析构函数设置为纯虚函数(pure virtual),拥有纯虚函数的类变为抽象基类(abstract class),抽象基类不能被实例化。如果某个class只希望作为base class(不希望被实例化),但是又没有一个纯虚函数,而base class应该有一个virtual析构函数,那么此时就可以将析构函数设置为纯虚函数。
必须为纯虚析构函数提供定义,否则会出现Link错误。因为析构函数的运作方式是,最深层派生(most derived)的那个class其析构函数最先被调用。然后是其每一个base class的析构函数被调用。
下面再给出一个例子:
#include<iostream> using namespace std; class CBird { public: CBird() { cout << "CBird constructor." << endl; } virtual ~CBird() {//多态基类的析构函数应该为虚函数 cout << "CBird destructor." << endl; } virtual void fly() {//基类CBird将fly()声明为virtrual,希望派生类重写(overriding)该方法 cout << "CBird fly." << endl; } }; class CLark : public CBird { public: CLark() { cout << "CLark constructor." << endl; } ~CLark() { cout << "CLark destructor." << endl; } void fly() { cout << "CLark fly." << endl; } }; int main() { //基类CBird类型指针指向派生类CLark类型对象 CBird * pBird = new CLark(); cout<<"1"<<endl; pBird->fly(); cout<<"2"<<endl; delete pBird; cout<<"3"<<endl; return 0; }
结果:
CBird constructor. CLark constructor. 1 CLark fly. 2 CLark destructor. CBird destructor. 3 Process returned 0 (0x0) execution time : 0.375 s Press any key to continue.
相关文章推荐
- C#与.net高级编程 C#的多态介绍
- C#中面向对象编程机制之多态学习笔记
- C#中的多态深入理解
- C#中多态、重载、重写区别分析
- 设计引导--一个鸭子游戏引发的设计理念(多态,继承,抽象,接口,策略者模式)
- c#基础学习之多态
- PHP面向对象三大特点学习(充分理解抽象、封装、继承、多态)
- 从汇编看c++中多态的应用
- C++虚析构函数的使用分析
- javascript 面向对象全新理练之继承与多态
- Java多态的使用注意事项
- C#使用虚拟方法实现多态
- 实例讲解PHP面向对象之多态
- C#中多态现象和多态的实现方法
- C++基础之this指针与另一种“多态”
- 深入解析C++中的虚函数与多态
- C++多态的实现及原理详细解析
- PHP5中实现多态的两种方法实例分享
- 举例讲解PHP面对对象编程的多态
- js中实现多态采用和继承类似的方法