虚表的写入时机以及虚表的二次写入

虚表的写入时机

虚表的写入时机指的是虚函数指针指向虚函数表的时机,在C++中创造一个对象要分为两步:1.开辟内存 2.调用构造函数 ,也就是说当完成第一个步骤后,对象的内存就已经被开辟完成了,那么内存中的虚函数指针是什么时候指向虚函数表的呢,首先我们假设虚函数指针是在构造函数之前指向虚函数表的。下面我们用一个例子来探讨这个问题。

include <iostream>

class Base	
{	
public:
	Base()
	{
		std::memset(this, 0, sizeof(this));
	}
	virtual void Show()
	{
		std::cout << "Base's show = "<< std::endl;
	}
};


int main()
{
	Base* pb = new Base;
	pb->Show();
	return 0;
}

我们先来自己分析一下整个过程,首先开辟内存,接着虚函数指针指向虚函数表,即

接着调用构造函数,我们在构造函数中将pb对象的所有成员赋值为0,即vfptr被赋值为NULL

此时vfptr已经不再指向vftable,那么接下来pb调用虚函数Show(),程序就会崩溃,因为vfptr在vftable中无法找到Show()函数的入口地址。而编译器执行的结果也正如我们所料

所以在这里我们可以得出结论,虚函数表在编辑阶段生成,虚函数指针在构造函数之前指向虚函数表,即虚表的写入是在构造函数之前进行的。

虚表的二次写入

所谓的虚表的二次写入指的是在继承关系中,派生类对象要调用两次构造函数,一次是基类的,另外一次是自己的,因此虚表要写入两次。

#include <iostream>

class Base
{
public:
	Base()
	{
		std::memset(this, 0, sizeof(this));
	}
	virtual void Show()
	{
		std::cout << "Base's show  "<< std::endl;
	}
};
class Derive :public Base
{
public:
	Derive(){}
	void Show()
	{
		std::cout << "Derive's show  " << std::endl;
	}
};

int main()
{
	Derive pd;
	Base* pb = &pd;
	pb->Show();
	return 0;
}

首先是在编译间段生成基类和派生类的虚函数表,注意,其实这里的派生类虚表不是真正的派生类的虚表,最终的派生类的虚表是派生类虚表和基类虚表合并以后的产物。

接着是派生类对象首先给从基类中继承下来的成员开辟内存,接着在调用基类的构造函数之前指向基类的虚表,

接下来调用基类的构造函数,将基类的成员全部赋值为0,即将基类的vfptr指向NULL

接着是派生类对象为自己的成员开辟内存

接着在调用派生类的构造函数之前将vfptr指向派生类的虚表,在这之前,要进行虚表合并,生成派生类最终的虚函数表

接着在调用派生类的构造函数之前,将派生类的虚函数指针指向派生类的最终虚函数表

最后进行虚函数指针的合并,由外向内合并

所以最后我们通过pb调用Show()函数,实际上是通过vfptr从虚函数表中找到Show()函数的入口地址,接着调用它,发生了动多态。

程序运行结果:

发布了88 篇原创文章 · 获赞 40 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/ThinPikachu/article/details/104481011
今日推荐