1. 程式人生 > 其它 ><九>理解虛繼承和虛基類

<九>理解虛繼承和虛基類

虛基類/抽象類

抽象類:有純虛擬函式的類

虛繼承
通過修飾繼承方式, 如程式碼2是虛繼承,被虛繼承的類稱為虛基類

虛繼承派生類的記憶體佈局方式
先是vbptr => 派生類的資料 =>基類的資料 ,
對比程式碼1和程式碼2,發現原本基類資料在前面,派生類資料在後面,但是在虛繼承的時候
基類資料方式放到了後面,前面放了vbptr和派生類資料.
vbprt指向的是vbtable ,vbtable中儲存的資料是偏移量, 是vbptr指標起始位置到基類的偏移量,見程式碼2和程式碼2後面的圖片
通過偏移量可以找到基類資料,仔細對比程式碼1和程式碼2

vfprt/vbptr
vftabe/vbtable

程式碼1

class  A{

public:
     int ma;
protcted:
     int mb;
private:
     int mc;

}

//B繼承 A,
class B : public A{
public:
     int md;
potected:
     int me;
private:
     int mf;
}

程式碼2 虛繼承

#include <iostream>
using namespace std;

class  A{

public:
     int ma;

protected:
     int mb;
private:
     int mc;

};

//B繼承 A,
class B : virtual public A{
public:
     int md;
protected:
     int me;
private:
     int mf;
};

int main(){

    return 0; 
        
}

程式碼3


#include <iostream>
using namespace std;

class  A{

public:
     int ma;
     virtual void show()
     {
     }
protected:
     int mb;
private:
     int mc;

};

//B繼承 A,
class B : public A{
public:
     int md;
     virtual void show()
     {
     }
protected:
     int me;
private:
     int mf;
};

int main(){
   A *PA=new B();
   PA->show();
   return 0;        
}

程式碼4

#include <iostream>
using namespace std;

class  A{

public:
     int ma;
     virtual void show()
     {
     }
protected:
     int mb;
private:
     int mc;

};

//B繼承 A,
class B : virtual  public A{
public:
     int md;
     virtual void show()
     {
     }
protected:
     int me;
private:
     int mf;
};

int main(){
   A *PA=new B();
   PA->show(); // 能正常呼叫B的show() 方法
   delete PA;  // 執行報錯! 如下圖
   return 0;        
}


vfptr/vbptr vbtable/vbtable 同時出現
當一個類有虛擬函式,那麼就會生成vfptr,vfptr指向vftable,vftable中主要包含RTTI資訊和虛擬函式地址資訊
vbptr 專門為派生類從基類中虛繼承用得,vbptr指向vbtable,vbtable中主要儲存了vbptr到虛基類地址的偏移量

執行報錯原因

PA->show();//正常
delete PA ;//執行報錯
A *PA=new B(); 用基類指標指向派生類,問題:new B()返回的地址是vbptr起始地址?還是基類vfptr的起始地址?
基類指標指向派生類物件,PA指向的是基類的起始地址,即上圖中vfptr起始地址,PA->show()能正常呼叫,因為
PA指向vfptr起始地址,直接可以將vfptr讀取出來,但是釋放記憶體的時候應該從vbptr地址開始釋放,所以報錯.

程式碼5

#include <iostream>
using namespace std;

class  A {

public:
	int ma;
        void operator delete(void *p) {
	    cout <<"A Operator Delete "<< p << endl;
	    free(p);
	}
	virtual void show()
	{
	}
protected:
	int mb;
private:
	int mc;

};

//B繼承 A,
class B : virtual public A {
public:
	int md;
	void * operator new(size_t size) {
		void * p = malloc(size);
		cout << "class B operator new malloc Address=" << p << endl;
		return p;
	}
	virtual void show()
	{
	}
protected:
	int me;
private:
	int mf;
};

int main() {
	A *PA = new B();
	cout << PA << endl;
        delete PA;
	system("pause");
	return 0;
}

結合程式碼5中申請的記憶體地址,和返回的地址,類的記憶體結構,偏移量,等資訊進行分析瞭解

如果程式碼5中改成如下

int main() {
        B b;
	A *PA = &b;
	system("pause");
	return 0;
}
b在棧上申請空間就不會有上面釋放記憶體的錯誤(windows vc編譯環境 ).

另外vfptr 是歸屬 基類還是派生類問題?
如果基類本身有虛擬函式的,那麼vfptr歸屬基類,如果基類中沒有虛擬函式,派生類有虛擬函式,那麼vfptr歸屬派生類 如下圖

vbtable中的偏移量是vbptr的起始地址到基類的偏移量