昨天在针对webcore中TimerBase的堆操作部分进行修改,目的在于解决切台死机的问题时引发了这个新问题,通过查找目前提到的最多的原因有两种:
(1)在基类的构造函数或析构函数中直接或间接的调用纯虚函数
(2)通过野指针调用到虚函数
并列举如下:
针对“在基类的构造函数或析构函数中直接或间接的调用纯虚函数 ”:
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" :
我们在现实中遇到的情况会比上面提到的复杂一些:一个子类对象在析构的过程中遇到异常而未完全销毁,从而遗留下一个"次品"对象,程序继续使用此次品对象而调用到纯虚函数:
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""的时候,可能离真正的出错点,也就是析构函数中抛出异常的点已经很远了。所以这种情况相对来讲比较难调试。
文章还提到了另外一篇分析更加透彻的文章:"Pure Virtual Function Called": An Explanation. 这是一篇相当全面的文章,从纯虚函数抽象基类讲起,介绍了对象模型中vptr及vtable的概念以及他们的构造析构过程。有了这些基础,作者然后列出了5中可能出现"pure virtual function call"的情况。
http://www.artima.com/cppsource/pure_virtual.html
有空再将其翻译出来学习下。
本文的基本观点引自:http://www.debuggingnow.com/blog/2010/03/pure-virtual-function-call-in-cpp.html