虛基類/抽象類 抽象類:有純虛函數的類 虛繼承 通過修飾繼承方式, 如代碼2是虛繼承,被虛繼承的類稱為虛基類 虛繼承派生類的記憶體佈局方式 先是vbptr => 派生類的數據 =>基類的數據 , 對比代碼1和代碼2,發現原本基類數據在前面,派生類數據在後面,但是在虛繼承的時候 基類數據方式放到了後面, ...
虛基類/抽象類
抽象類:有純虛函數的類
虛繼承
通過修飾繼承方式, 如代碼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的起始地址到基類的偏移量