现在的位置: 首页 > 综合 > 正文

Effective c++学习笔记——条款07:为多态基类声明virtual析构函数

2013年12月03日 ⁄ 综合 ⁄ 共 4864字 ⁄ 字号 评论关闭

 

Declare destructors virtual in polymorphic base classes
       1、为什么要申明虚函数
       C++程序设计中通常会存在一个基类有多个派生类型,而基类中的存在的大都是纯虚函数,需要派生类型实现。而这样的情况下,通过使用factory模式返回一个基类型的指针。在C++中明确指出,一个派生类型经过由一个基类型指针被删除,而该基类型带着一个non-virtual析构函数其结果未定义。只会造成一个局部的销毁,即基类型资源被释放,而派生类型造成memory
leak,看下面的代码:

  1. // Virtual_Const.cpp : 定义控制台应用程序的入口点。
      
  2. //   
  3.   
  4. #include "stdafx.h"   
  5. #include <iostream>
      
  6. using namespace std;  
  7.   
  8. class Base{  
  9. public:  
  10.     Base(){}  
  11.     ~Base(){ cout<<"~Base()"<<endl;}////不带virtual时输出的结果请看图一
      
  12.     //virtual ~Base(){ cout<<"~Base()"<<endl;}////正确的做法带virtual时输出的结果请看图二
      
  13. };  
  14.     class Derived:public Base{  
  15.     public:  
  16.         Derived()  
  17.         {  
  18.             p=(char*)malloc(sizeof(char)*10);  
  19.         }  
  20.         ~Derived()  
  21.         {  
  22.             free(p);  
  23.             cout<<"~Derived()"<<endl;  
  24.         }  
  25.     private:  
  26.         char *p;  
  27.     };  
  28.     int _tmain(int argc, _TCHAR* argv[])  
  29.     {  
  30.         Base *ptr=new Derived();  
  31.         if (ptr==NULL)  
  32.         {  
  33.             cout<<"未分配成功"<<endl;  
  34.             exit(1);  
  35.         }  
  36.         delete ptr;  
  37.         return 0;  
  38.     }  

图一:不带virtual时输出结果:
图二:带virtual时输出结果:
 
以上代码和输出结果可以看出,Derived类继承了Base类,并且在heap上申请了内存资源,在它的析构函数中被释放。但由于Base的析构函数是non-virtual,所以根本没有正确释放Derived类型中的ptr指针。
2、virtual 函数实现内部机制
       
         polymorphic(带多态性质的)base classes应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数。Classes的设计目的如果不是作为base
classes使用,或不是为了具备多态性(polymorphically),就不应该声明virtual析构函数。
那么是不是所有的class都加上virtual析构函数以保万一,事实不是如此。如果没有必要,请不要为析构函数加上virtual二字。原因是virtual是有代价的,为了实现virtual函数,类中间必须要增加一个pointer指向虚函数表,这样增大了类的体积。所以没有必要的话,还是不要随意声明virtual的析构函数。普遍的规则是只有当类当中有virtual的函数时,析构函数才声明为virtual。也就是说这个基类是有多态性质(polymorphic)的。
        虚函数的实现需要它所在的对象包含额外的信息,这一信息用来在运行时确定本对象需要调用哪个虚函数。通常,这一信息采取一个指针的形式,这个指针被称为“ vptr ”(“虚函数表指针”)。 vptr 指向一个包含函数指针的数组,这一数组称为“ vtbl ”(“虚函数表”),每个包含虚函数的类都有一个与之相关的 vtbl 。当一个虚函数被一个对象调用时,就用到了该对象的
vptr 所指向的 vtbl ,在 vtbl 中查找一个合适的函数指针,然后调用相应的实函数。
请看如下代码,就知道声明virtual函数是要浪费系统资源的。

 

  1. // sev_vir.cpp : 定义控制台应用程序的入口点。
      
  2. //   
  3. #include "stdafx.h"
      
  4. #include <iostream>   
  5. using namespace std;  
  6. class class1{};  
  7. class class2  
  8. {  
  9. public:  
  10. virtual ~ class2();  
  11. };  
  12. int _tmain(int argc, _TCHAR* argv[])  
  13. {  
  14.     cout<<sizeof(class1)<<endl;  
  15.     cout<<sizeof(class2)<<endl;  
  16.     return 0;  
  17. }  

3、请注意

       不过还有一个问题,那就是继承那些不带virtual函数的标准类库的类,比如string类。如果你继承了string类,那么当你使用string的指针释放你的继承类的话,依然存在上面描述的问题,你的继承类没有释放它该释放的空间。因为string没有virtual的析构函数。所以请不要去继承这些并没有virtual析构函数的类。

        还有就是STL中的容器通常继承都毫无意义,因为它们也都没有提供virtual析构函数。C++并没有提供像JAVA或者C#的final和sealed这样的约束关键字来禁止某个类型被继承。 并不是每个类型都会被用来做继承用途,也不是每个类型设计出来被用来做为多态用途,所以正确的设计相当重要。

4、纯虚函数实现

  1. // Pure_vir.cpp : 定义控制台应用程序的入口点。
      
  2. //   
  3. #include "stdafx.h"
      
  4. #include <iostream>   
  5. using namespace std;  
  6. class Base //abstract class
      
  7. {  
  8. public:  
  9.     virtual ~Base(){};//virtual, not pure
      
  10.     virtual void func() const = 0;//pure virtual
      
  11.       
  12. };  
  13. void Base::func() const //pure virtual also can have function body
      
  14. {  
  15.     cout <<"Base::func"<<endl;  
  16. }  
  17. class Derived : public Base  
  18. {  
  19. public:  
  20.     Derived(){}  
  21.     virtual void func() const  
  22.     {  
  23.         Base::func();  
  24.         cout <<"Derived::func"<<endl;  
  25.     }  
  26.     virtual void foo(){}  
  27. };  
  28. int _tmain(int argc, _TCHAR* argv[])  
  29. {  
  30.     Base* pb=new Derived();  
  31.     pb->func();  
  32.     pb->Base::func();  
  33.     return 0;  
  34. }   

  从生成的结果我们可以看到有纯虚函数的类是抽象类,不能生成对象,只能派生。

        如果他派生的类的纯虚函数没有被改写,那么,它的派生类还是个抽象类。定义纯虚函数就是为了让基类不可实例化化, 因为实例化这样的抽象数据结构本身并没有意义。或者给出实现也没有意义实际上我个人认为纯虚函数的引入 ,为了安全.因为避免任何需要明确但是因为不小心而导致的未知的结果,提醒子类去做应做的实现.。

请记住

1、polymorphic(带多态性质的) base classes应该声明一个virtual 析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数。

2、Classes的设计目的如果不是作为base classes使用,或不是为了具备多态性质(polymorphically),就不该声明virtual析构函数。

抱歉!评论已关闭.