关于虚析构函数的一个问题(vc6.0)

effective c++中14条:基类的析构函数应该为虚函数

一般的:

 
  
  1. class Base 
  2. public: 
  3.     void fun(){}  //1 
  4.     ~Base()      //2 
  5.     { 
  6.         cout << "Base" << endl
  7.     } 
  8.  
  9. class Drived : public Base 
  10. public: 
  11.     ~Drived()   //3 
  12.     { 
  13.         cout << "Drived" << endl
  14.     } 
  15. Base *pB = new Drived; 
  16. delete pB;  //4 
  17.  
  18. //pB->~Base();   //5 
  19. //operator delete(pB); //6,清除内存 

5和6可以看做是4的分解步骤。

    2和3都不为虚函数,则4只会析构Base部分,但不会报错;2和3都为虚构函数,则4可以调用Drived类的析构函数;2不为虚析构函数,3为虚析构函数,则4则会出错;2不为虚函数,3为虚函数,而1为虚函数,则执行4编译器不会报错,只会有内存泄露。

   根据以上,编译器报错应该和虚函数表的构建有关。基类有虚表时,析构函数不为虚,派生类为虚,则不错报错,执行4有内存泄露。而且delete可分解为5和6步,2不为析构函数,3为析构函数,则4则会出错则是出现在6步,而5步仍可执行。operator delete和虚表的创建过程之间的影响产生了这个问题。

   虚函数是通过虚表实现的,派生类的对象的头四个结点为虚表指针,指向虚表。构建对象时先构建基类,基类的虚函数加入虚表,再构建派生类数据,派生的虚函数加入虚表,派生类覆盖基类的虚函数则替代基类的虚表中的虚函数指针。通过虚表,最终实现了多态。

虚表方面知识参考:http://www.douban.com/group/topic/10733547/

  在2不为虚析构函数,3为虚析构函数时,调用4时,通过下面程序:

 
  
  1. Drive *d = new Drive; 
  2. Base *b = d; 
  3. cout << d << endl; 
  4. cout << b << endl; 

 d的结果为0x00380FE0,而b的结果为0x00380FE4,说明b和d指针是指向不同地方的,d指针指向从虚表开始,而b指针则跳过了虚表。这样5调用delete的时候,他类型类Base*,且基类没有虚析构函数,因此首先会调用pB->~Base(),之后编译器发现该指针指向派生类,而派生类对象中有虚表(pB指针当前不是指向虚表地址),编译器就会将这种情况和动态绑定情况混淆而前面已经调用了基类的析构函数,因此编译器会报错。这就解释了为什么1为虚函数,编译器同样不报错的原因,注意这时同同样不会调用派生类的析构函数,内存会泄露,因为基类本身就不是虚析构函数。