一、源码
#include <iostream>
class Father
{
public:
Father()
{
/**
* 该处直接将该对象清零,意味着虚函数表指针亦被清零。
*/
memset(this, 0, sizeof(Father));
}
public:
virtual void Func1()
{
std::cout << "Fahter::Func1()" << std::endl;
}
virtual void Func2()
{
std::cout << "Fahter::Func2()" << std::endl;
}
virtual void Func3()
{
std::cout << "Fahter::Func3()" << std::endl;
}
};
int main()
{
Father father;
father.Func1();
father.Func2();
father.Func3();
Father* pfather = new Father();
/**
* 此处开始崩溃,因为虚函数表指针已经被清零。
*/
pfather->Func1();
pfather->Func2();
pfather->Func3();
return 0;
}
结果
二、分析
问题,为什么含有虚函数的类,若是直接申请为实例对象,可以调用虚函数,但是通过指针 new 出的实例却崩溃呢?
原因,这里有个静态联编和动态联编的概念。
静态联编的意思是在编译期间就能确定调用的函数的地址,即:call (地址),地址是确定的值。
动态联编的意思是在运行期间才能确定调用的函数的地址。方法是通过对象的虚函数表指针找到虚函数表,再通过虚函数表找到虚函数,这也是多态的实现机理。而多态的实现,只针对指针或者引用。
上述代码中,因为对象“father”不是指针或者引用,所以尽管存在虚函数,但是这些虚函数在不用在多态的情况下,其功能和普通函数是一样的,所以编译器直接将其按照静态编译来处理。
针对上述代码中的"pfather",因为是 new 出的对象,所以编译器要考虑下面多态的可能性,所以调用虚函数是按照动态联编的调用顺序来调用的,即:虚函数表指针 --> 虚函数表 --> 虚函数 。但是,问题出在了“Father”类的构造函数中,因为该构造函数初始化对象的方法是直接将整个对象都初始化为0,这里面也包括虚函数表指针,所以,走多态的路来调用虚函数直接 Over 了。。所以上述通过 pfather 调用虚函数的方法就产生了崩溃。
(SAW:Game Over!)