我們先來看一個代碼,這是在繼承與虛函數(shù)學(xué)生過程中發(fā)生的一個錯誤,涉及到了C++的對象內(nèi)存的知識,因為這方面知識比較復(fù)雜,這里不做過多的介紹,只簡單分析一下出錯原因。
class Base
{
public:
void fun()
{
Cout << “Base::fun()” << endl;
}
~Base() //虛析構(gòu)函數(shù)
{
cout << ”Base::~Base” << endl;
}
};
class Child:public Base //繼承Base類
{
Public:
virtual void fun() //虛函數(shù)
{
Cout << “Child:fun()” <<endl;
}
};
int main()
{
Base *p = new Child; //新建一個子類的對象,賦值給一個父類指針
p->fun();
delete p; //通過父類指針釋放內(nèi)存
return 0;
}
VS運(yùn)行程序時,發(fā)生如下錯誤:
通過提示發(fā)現(xiàn),VS提示應(yīng)該是內(nèi)存方面的錯誤。而且對于上面的代碼來說,父類中的fun函數(shù)不是虛函數(shù),而子類中的fun函數(shù)是虛函數(shù),所以p->fun也是會調(diào)用父類的fun函數(shù)。
那么為什么會出現(xiàn)內(nèi)存釋放的錯誤呢?重新寫一個main函數(shù)如下:
int main()
{
Child *c = new Child; //在堆上創(chuàng)建一個子類對象
Base *p = c; //將子類對象指針賦值給一個父類對象指針
cout << c << " " << p << endl; //打印信息
p->fun(); //通過父類指針來調(diào)用fun函數(shù)
delete p; //通過父類指針釋放內(nèi)存
return 0;
}
再次運(yùn)行以上代碼,結(jié)果如下:
我們從打印的信息可以看到,Child對象指針值為0x01393FD0,Base對象指針為0x1393FD4,通過打印信息發(fā)現(xiàn)兩個值并不一樣,這樣釋放內(nèi)存時產(chǎn)生了錯誤。
原因如下:
- 存在虛函數(shù)的類對象中會隱藏了一個虛指針,虛指針存放在對象內(nèi)存的開始位置,這里子類中有一個虛指針而父類中沒有;
- 子類中有一塊內(nèi)存布局和父類對象的內(nèi)存布局是一樣的,但是這塊內(nèi)存肯定不是子類對象的起始位置,所以將子類對象指針賦值給一個父類對象指針時,為了操作上不產(chǎn)生錯誤,會把這塊和父類內(nèi)存布局相同的位置賦值給父類指針,因而發(fā)生了內(nèi)存的偏移;
- 在堆上分配的子類對象內(nèi)存,如上面代碼起始地址是0x01393FD0,這里是通過delete父類指針來釋放內(nèi)存,而父類指針的值為0x1393FD4,這樣釋放內(nèi)存中檢測不是正確的起始位置而發(fā)生了錯誤。
解決方法:
要解決以上問題,就要想辦法讓子類指針賦值給父類指針時,兩個指針的值是一個樣,這里我們可以在父類中設(shè)置任意一個虛函數(shù)(將父類中的fun設(shè)置為虛函數(shù)或者將父類的析構(gòu)函數(shù)設(shè)置為虛函數(shù)),這樣父類和子類中都有虛指針,賦值時不會發(fā)生地址的偏移。
為了防止出現(xiàn)以上錯誤,我們代碼中一定要多加注意。盡量不要在子類中設(shè)置虛函數(shù)和父類中的普通函數(shù)重名,另外還有一個編碼小技巧,如果一個類中有虛函數(shù)或者純虛函數(shù)時,要將其析構(gòu)函數(shù)設(shè)置為虛析構(gòu)函數(shù),以防發(fā)生內(nèi)存泄漏等問題。
本文版權(quán)歸傳智播客C++培訓(xùn)學(xué)院所有,歡迎轉(zhuǎn)載,轉(zhuǎn)載請注明作者出處。謝謝!
作者:傳智播客C/C++培訓(xùn)學(xué)院
首發(fā):http://8y3kgpwe.cn/c/