<九>理解虛繼承和虛基類
阿新 • • 發佈:2022-11-29
虛基類/抽象類
抽象類:有純虛擬函式的類
虛繼承
通過修飾繼承方式, 如程式碼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的起始地址到基類的偏移量