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