1 #include<iostream> 2 using namespace std; 3 4 class ClxBase 5 { 6 public: 7 ClxBase() {}; 8 virtual ~ClxBase() { cout<<"delete ClxBase"<<endl; }; 9 10 virtual void DoSomething() { cout << "Do something in class ClxBase!" << endl; }; 11 12 }; 13 14 class ClxDerived : public ClxBase 15 { 16 public: 17 ClxDerived() {}; 18 ~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; }; 19 20 void DoSomething() { cout << "Do something in class ClxDerived!" << endl; }; 21 22 }; 23 24 int main(int argc, char const* argv[]) 25 { 26 ClxBase *pTest = new ClxDerived; 27 pTest->DoSomething(); 28 delete pTest; 29 return 0; 30 }
但是,如果把类ClxBase析构函数前的virtual去掉,那输出结果就是下面的样子了:
没有调动子类的析构函数
也就是说,类ClxDerived的析构函数根本没有被调用!一般情况下类的析构函数里面都是释放内存资源,而析构函数不被调用的话就会造成内存泄漏。我想所有的C++程序员都知道这样的危险性。当然,如果在析构函数中做了其他工作的话,那你的所有努力也都是白费力气。
所以,文章开头的那个问题的答案就是--这样做是为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。
当然,并不是要把所有类的析构函数都写成虚函数。因为当类里面有虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间。所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数。
总结一下虚析构函数的作用:
(1)如果父类的析构函数不加virtual关键字
当父类的析构函数不声明成虚析构函数的时候,当子类继承父类,父类的指针指向子类时,delete掉父类的指针,只调动父类的析构函数,而不调动子类的析构函数。
(2)如果父类的析构函数加virtual关键字
当父类的析构函数声明成虚析构函数的时候,当子类继承父类,父类的指针指向子类时,delete掉父类的指针,先调动子类的析构函数,再调动父类的析构函数。
二.虚析构函数的原理分析
1 #include<iostream> 2 using namespace std; 3 4 class Base 5 { 6 public: 7 Base(){cout<<"create Base"<<endl;} 8 virtual ~Base(){cout<<"delete Base"<<endl;} 9 }; 10 11 class Der : public Base 12 { 13 public: 14 Der(){cout<<"create Der"<<endl;} 15 ~Der(){cout<<"Delete Der"<<endl;} 16 }; 17 int main(int argc, char const* argv[]) 18 { 19 Base *b = new Der; 20 delete b; 21 22 return 0; 23 }
从创建讲起,用gdb调试你会发现,
(1)先调用父类的构造函数,再调用子类的构造函数
这里有一个问题:父类的构造函数/析构函数与子类的构造函数/析构函数会形成多态,但是当父类的构造函数/析构函数即使被声明virtual,子类的构造/析构方法仍无法覆盖父类的构造方法和析构方法。这是由于父类的构造函数和析构函数是子类无法继承的,也就是说每一个类都有自己独有的构造函数和析构函数。
(2)而由于父类的析构函数为虚函数,所以子类会在所有属性的前面形成虚表,而虚表内部存储的就是父类的虚函数
(3)当delete父类的指针时,由于子类的析构函数与父类的析构函数构成多态,所以得先调动子类的析构函数;之所以再调动父类的析构函数,是因为delete的机制所引起的,delete 父类指针所指的空间,要调用父类的析构函数。
所以结果就是这样