zoukankan      html  css  js  c++  java
  • 虚函数调用错误

    首先贴另外一个地址https://blog.csdn.net/kikikind/article/details/2645316

    一、理论上case

    当一个纯虚函数被调用到时,vc++的debug模式下会弹出这么一个对话框:

    这里没拷贝到。

    然后就是crash了。

    在网上找了一下,发现已经有人对此作了详细的介绍:"Pure Virtual Function Called": An Explanation. 这是一篇相当全面的文章,从纯虚函数抽象基类讲起,介绍了对象模型中vptr及vtable的概念以及他们的构造析构过程。有了这些基础,作者然后列出了5中可能出现"pure virtual function call"的情况,其实可以总结为两种:

    • 在基类的构造函数或析构函数中直接或间接的调用纯虚函数 
      举个在基类构造函数中间接调用纯虚函数的例子: 
      class Base
      {
      public:
      	Base(){callVirtual();}
      	void callVirtual(){virtualFunc();}
      	virtual void virtualFunc() = 0;
      };
      class Derived: public Base
      {
      public:
      	virtual void virtualFunc(){}
      };
      
      Derived d; //构造过程中调用到纯虚函数
    • 通过野指针调用到虚函数  
        还是上面那个例子,但是不在基类构造函数中调用callVirtual: 
      Derived* pD = new Derived;
      Base* pB = pD;
      delete pD;
      pB->virtualFunc();

    其实对于第一种情况,如果你在基类构造函数或析构函数中直接调用纯虚函数,编译器应该能捕捉到这个错误;间接的调用虽然编译器无法检测到,但是由于Scott同学在<Effective C++>中的大力宣传:Item 9: Never call virtual functions during construction or destruction,这种情况发生的概率应该比较小,况且即使发生了,排起错来相对比较简单。

    而对于第二种情况,虽然野指针的行为是未定义的,但就我所了解的,我们一般会得到一个"access violation",而不是"pure virutal function call" :

    二、现实中的case

    我们在现实中遇到的情况会比上面提到的复杂一些:一个子类对象在析构的过程中遇到异常而未完全销毁,从而遗留下一个"次品"对象,程序继续使用此次品对象而调用到纯虚函数:

    class Base
    {
    public:
    	~Base(){throw 0;}  // . . . a)
    	virtual void virtualFunc() = 0;
    };
    class Derived: public Base
    {
    public:
    	virtual void virtualFunc(){}
    };
    
    Base* pB = new Derived;
    
    __try
    {
    	delete pB;         // . . . b)
    	pB = NULL;
    }
    __except(EXCEPTION_EXECUTE_HANDLER){
    }
    
    
    pB->virtualFunc();       // . . . c) 

    在b)处析构Derived对象的时候,在其基类析构函数中a)处抛出了异常,而此时,因为Derived的析构函数已经调用完毕,该对象中的vptr已经指向基类的vtable,从而形成了一个按照正常流程无法构造出来的"次品"对象,当你使用该对象在c)处来调用virtualFunc时,自然导致 "pure virtual function call"的错误。

    需要注意的是,这里,你遇到  "pure virtual function call""的时候,可能离真正的出错点,也就是析构函数中抛出异常的点已经很远了。所以这种情况相对来讲比较难调试。

    三、析构函数与异常

    好吧,我知道你忍了好久了,你早想喊出来:"你本来就不该在析构函数中抛出异常!",就像Scott同学说的:Item 11:  Prevent exceptions from leaving destructors;就像C++ FAQ中说的:Never throw an exception from a destructor.

    虽然,也有人站出来说,there is nothing wrong with throwing destructors,但我还是支持你的观点,我们的确不应该在析构函数中抛出异常,不然,我们不得不面对以下两个严重的问题:

    • 二次异常导致程序退出;
    • 遗留下来的未完全销毁的对象与未完成的工作导致的后续问题 
      pure virtual function call就是这种情况。

    但是理想与现实总是有差距的,有些事情,你总得面对:

    1. 10多年,几百万行代码,无数人维护过的code base,谁都不敢保证是否某个析构函数会直接或间接的抛出异常。
    2. 或许我们应该对第1种情况,也就是我们自己的代码负责,对原有代码做一次全面的检查,并保证之后的代码不会在析构函数中抛出异常。可是即使如此,如果我们在析构函数中调用了第三方的库函数,而该函数会抛出异常呢?
    3. 即使我们调用到的函数( 包括自己的和第三方的)不会显式的抛出异常,当我们用SEH处理异常时,如果代码中出现除0操作,access violation等,都还是会被当做异常捕获的。
    4. 那么如果在每个non-trivial的析构函数中都加上异常处理呢?这样代码未免也太ugly了。况且在保证不主动抛出异常的前提下,这样的代码只是以防万一,意义不是很大。

    所以,要在析构函数中完全避免异常还是蛮纠结的。Herb Sutter曾就C++语言提出过一个提议:让析构函数无法抛出异常,从语言级别上去解决,但被Bjarne Stroustrup,  Andy Koenig等人否决了。因为这会导致原有程序的行为不一致,况且在极少数的情况下,我们还是希望能抛出异常来的。

  • 相关阅读:
    HDU 2852 KiKi's K-Number (主席树)
    HDU 2089 不要62
    Light oj 1140 How Many Zeroes?
    Bless You Autocorrect!
    HDU 6201 transaction transaction transaction
    HDU1561 The more ,The better (树形背包Dp)
    CodeForces 607B zuma
    POJ 1651 Mulitiplication Puzzle
    CSUOJ 1952 合并石子
    Uva 1599 Ideal path
  • 原文地址:https://www.cnblogs.com/wangshaowei/p/11397645.html
Copyright © 2011-2022 走看看