zoukankan      html  css  js  c++  java
  • 虚析构函数、虚函数

    1.[Effective C++原则07]:为多态基类声明virtual 析构函数。

    [如果不]: 如果不声明为析构函数,可能出现的结果如下:Derived对象的成分没有被销毁,形成资源泄露、在调试上会浪费很长时间。

     

    class CSimpleClass  

    1. {  
    2. public:  
    3. CSimpleClass(){ cout << "CSimpleClass" <<endl; }  
    4. ~CSimpleClass() { cout <<"~CSimpleClass" << endl; }  
    5. private:  
    6. };  
    7.    
    8. class CDerived : public CSimpleClass  
    9. {  
    10. public:  
    11. CDerived() { cout << "CDerived" << endl; }  
    12. ~CDerived() { cout << "~CDerived" << endl; }  
    13. private:  
    14. };  
    15.    
    16. int main()  
    17. {  
    18. CSimpleClass *pSimple = new CDerived;  
    19. delete pSimple;  
    20.    
    21. return 0;  
    22. }  

    执行结果如下:


    显然,CDerived 对象没有被析构!

    1、 造成上述不同的原因何在?

    “C++标准”明确指出,当派生类对象经由一个基类指针pBaseObject被删除,而该基类带有一个non-virtual析构函数,其结果未有定义(即不可预知)。实际执行时,如上面第一个图示,会产生bug,派生类的对象没有被销毁

    这就形成诡异的“局部销毁”对象,形成资源泄露

     

    2、 什么时候需要基类析构函数声明为虚函数?什么时候不需要基类的析构函数为虚函数?

           该问题涉及析构函数何时应该为虚函数。注意:对于上面的基类BaseClass,

           若析构函数不为虚函数,sizeof(BaseClass) = 1。

           若析构函数为虚函数,sizeof(BaseClass) = 4。

           至于为什么包含构造函数、非虚析构函数的类的大小为1个字节。解释如下:

           空类类的大小比如BaseClass没有构造、析构函数,本来sizeof(BaseClass)应该为0,但是我们声明该类型实例的时候,必须在内存中占用一定的空间,否则无法使用这些实例。至于占用多少内存,由编译器决定,visual studio中每个空类型的实例占用1个字节的空间

           而加上构造函数、析构函数或其他非虚类型的函数以后呢?由于这些非虚类型的函数的地址只与类有关,而与类的实例无关,编译器不会因为非虚函数的增加而添加任何额外的信息。

           那么为什么析构函数变成虚函数后,大小就变成4个字节了呢?主要原因是:C++一旦发现类中有虚函数,就会为该类生成虚函数表,并在该类型的每一个实例中添加指向虚函数表的指针。在32位机器上,一个指针占4个字节的空间,所以求sizeof大小为4。而在64位机器上,一个指针占用8个字节的空间,因此sizeof大小为8。

           即为类析构函数声明为虚析构函数是以付出内存为代价的。所以,无端将所有类的析构函数声明为虚函数,就向从未声明它们是虚函数一样,都是错误的。

     

    总结如下:

    (1)带多态性质的基类应用声明一个虚析构函数。如果类中带有任何虚函数,它就应该拥有一个虚析构函数;

    (2)设计类的目的如果不作为基类,或者不是为了具备多态性,就不应该声明虚析构函数。

    ——参考《Effective C++》条款7;《剑指Offer》

    2.[Effective 原则09]:绝不在构造和析构过程中调用virtual函数。

    【原因】:base class的执行更早于derived class的构造函数,当base class的构造函数执行的时候derived class的成员变量尚未初始化。

    【如果不】:执行的结果不会动态联编,依然执行其所在层的虚函数。

    【示例如下】:

    1. class CSimpleClass  
    2. {  
    3. public:  
    4.        CSimpleClass() { cout << "CSimpleClass"<< endl; foo();} //调用了本层的foo   
    5.        virtual ~CSimpleClass() { cout <<"~CSimpleClass" << endl; foo();} //调用了本层的foo   
    6.        virtual void foo() { cout << "CSimpleClass::foo()" << endl; }  
    7. private:  
    8. };  
    9.    
    10. classCDerived : public CSimpleClass  
    11. {  
    12. public:  
    13.        CDerived() { cout <<"CDerived" << endl; foo(); }  
    14.        ~CDerived() { cout <<"~CDerived" << endl; foo(); }  
    15.        void foo() { cout<< "CDerived::foo()" << endl; }  
    16. private:  
    17. };  
    18.    
    19. int main()  
    20. {  
    21.        CSimpleClass *pSimple = new CDerived;  
    22.        delete pSimple;  
    23.    
    24.        return 0;  
    25. }   

    执行结果如下:


    3.综合1,2的笔试题如下:

    1. class CBase  
    2. {  
    3. public:  
    4.        CBase(){ cout << "CBase ctor" << endl; foo(); }    //调用本层的foo   
    5.         ~CBase() { cout<< "CBase dtor" << endl; foo(); }  //未加virtual,且调用本层的foo   
    6. private:  
    7.        virtualvoid foo(){ cout << "Base::foo()" << endl; } //   
    8. };  
    9.    
    10. class CDerived : public  CBase  
    11. {  
    12. public:  
    13.        CDerived(){cout << "CDerived ctor" << endl; foo(); }  
    14.        ~CDerived(){cout << "CDerived dtor" << endl; foo(); }  
    15.    
    16. private:  
    17.        virtual void foo(){ cout << "Derived::foo()" << endl; }  
    18. };  
    19.    
    20. int main()  
    21. {  
    22.        CBase*pBase = new CDerived;  
    23.        delete pBase;  
    24.    
    25.        return 0;  
    26. }  

    结合原则1,2.正确的输出结果是:


    显然,1.CDerived的析构函数不会被调用,因为CBase的析构函数非虚函数。

    2.在CBase的构造和析构函数中调用虚函数,仅会执行本层的定义,不会下调。还是遵照[Effective 原则09]:绝不在构造和析构过程中调用virtual函数为上策。

  • 相关阅读:
    字节码编程,Javassist篇四《通过字节码插桩监控方法采集运行时入参出参和异常信息》
    字节码编程,Javassist篇三《使用Javassist在运行时重新加载类「替换原方法输出不一样的结果」》
    字节码编程,Javassist篇二《定义属性以及创建方法时多种入参和出参类型的使用》
    字节码编程,Javassist篇一《基于javassist的第一个案例helloworld》
    CPU瞒着内存竟干出这种事
    可怕!CPU竟成了黑客的帮凶!
    完了!CPU一味求快出事儿了!
    如果平行宇宙也有编程语言
    你离黑客的距离,就差这20个神器了
    哈希表哪家强?几大编程语言吵起来了!
  • 原文地址:https://www.cnblogs.com/davy2013/p/3148973.html
Copyright © 2011-2022 走看看