單繼承與多繼承的虛函數表

我們大部分時候使用的多態虛表實在public繼承的基礎上實現的
這句話可以怎麼說了?
自己簡單的思考邏輯認爲基類中的成員或者成員函數子類中都會重新拷貝一份,也就是如果派生類中沒有定義基類的某虛函數重寫但是這個**虛函數的地址已經被寫實拷貝寫了一份放到派生類·虛表當中了**

  • 好了,來看一下一種情況下的虛表情況吧!
  • 單繼承虛表
class Base
{
public:
	virtual void func1()
	{ 
		cout << "Base::func1" << endl;
	}
	virtual void func2()
	{
		cout << "Base::func2" << endl;
	} 
private:  
	int a;
};

class Derive :public Base
{
public:   
	virtual void func1()
	{
		cout << "Derive::func1" << endl;
	}
	virtual void func3() 
	{
		cout << "Derive::func3" << endl;
	}
	virtual void func4()
	{
		cout << "Derive::func4" << endl;
	}
private:  
	int b;
};
int main()
{
	Base b;
	Derive d;
	return 0;
}
  • !!!看一下這裏問題就出來了,和理論之中存在差別,基類子類當中的自己虛函數並沒有顯示出來
  • 這裏是編譯器的監視窗口故意隱藏了這兩個函數, 也可以認爲是他的一個小bug。那麼我們如何查看d的虛表呢?下面我們使用代碼打印出虛表中的函數。
    在這裏插入圖片描述
    模擬定義一個虛表指針,來把虛表元素打印出來
class Base
{
public:
	virtual void func1()
	{ 
		cout << "Base::func1" << endl;
	}
	virtual void func2()
	{
		cout << "Base::func2" << endl;
	} 
private:  
	int a;
};

class Derive :public Base
{
public:   
	virtual void func1()
	{
		cout << "Derive::func1" << endl;
	}
	virtual void func3() 
	{
		cout << "Derive::func3" << endl;
	}
	virtual void func4()
	{
		cout << "Derive::func4" << endl;
	}
private:  
	int b;
};
typedef void(*VFPTR) ();
void PrintVTable(VFPTR vTable[]) 
{    
	cout << " 虛表地址>" << vTable << endl;   
	for (int i = 0; vTable[i] != nullptr; ++i) 
	{
		printf(" 第%d個虛函數地址 :0X%x,->", i, vTable[i]);        
		VFPTR f = vTable[i];
		f();   
	}
	cout << endl;
}

int main()
{
	Base b;
	Derive d;
	VFPTR* vTableb = (VFPTR*)(*(int*)&b);   
	PrintVTable(vTableb);

	VFPTR* vTabled = (VFPTR*)(*(int*)&d);  
	PrintVTable(vTabled);

	return 0;
}

在這裏插入圖片描述

  • 思路:取出b、d對象的頭4bytes,就是虛表的指針,前面我們說了虛函數表本質是一個存虛函數指針的 指針數組,這個數組最後面放了一個nullptr
  • 1.先取b的地址,強轉成一個int*的指針
  • 2.再解引用取值,就取到了b對象頭4bytes的值,這個值就是指向虛表的指針
  • 3.再強轉成VFPTR*,因爲虛表就是一個存VFPTR類型(虛函數指針類型)的數組。
  • 4.虛表指針傳遞給PrintVTable進行打印虛表
  • 5.需要說明的是這個打印虛表的代碼經常會崩潰,因爲編譯器有時對虛表的處理不乾淨,虛表最後面沒有 放nullptr,導致越界,這是編譯器的問題。我們只需要點目錄欄的-生成-清理解決方案,再編譯就好了。
  • 多繼承虛表
class Base1 
{
public:  
	virtual void func1() 
	{
		cout << "Base1::func1" << endl; } 
	virtual void func2()
	{
		cout << "Base1::func2" << endl;
	}
private:
	int b1;
};

class Base2
{
public:   
	virtual void func1() 
	{
		cout << "Base2::func1" << endl;
	}
	virtual void func2() 
	{
		cout << "Base2::func2" << endl;
	}
private:
	int b2;
};

class Derive : public Base1, public Base2 
{
public:   
	virtual void func1()
	{
		cout << "Derive::func1" << endl;
	}
	virtual void func3() { 
		cout << "Derive::func3" << endl;
	}
private:    int d1;
};

typedef void(*VFPTR) (); 
void PrintVTable(VFPTR vTable[]) {
	cout << " 虛表地址>" << vTable << endl;    
	for (int i = 0; vTable[i] != nullptr; ++i)    
	{
		printf(" 第%d個虛函數地址 :0X%x,->", i, vTable[i]);  
		VFPTR f = vTable[i];   
		f(); 
	} 
		
		cout << endl;
}

int main() {
	Derive d;

	VFPTR* vTableb1 = (VFPTR*)(*(int*)&d); 
	PrintVTable(vTableb1);

	VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d + sizeof(Base1)));
	PrintVTable(vTableb2);

	return 0;
}

在這裏插入圖片描述
在這裏插入圖片描述
可以看出:多繼承派生類的未重寫的虛函數放在第一個繼承基類部分的虛函數表中

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章