昨天去xx公司面試,面試官問了乙個關於c++類析構函式為虛函式時,如果是父類的指標用子類來new,如果發生析構時,析構函式是virtual與不是virtual有什麼區別。當時答的不好,回來總結了一下,在機器上實現了一遍,終於搞明白了。記錄下來,以後遇到這種情況自己一定不要犯錯了
一、先看第一種最簡單的情況吧,教科書上教的,析構函式不是virtual,正常定義乙個子類物件
class student
~student()
};
class gradeonestue:public student
~gradeonestue()
};
int _tmain(int argc, _tchar* argv)
這時構造順序是先1後2,下面是反彙編**
gradeonestue()
00411470 push ebp
00411471 mov ebp,esp
00411473 sub esp,0cch
00411479 push ebx
0041147a push esi
0041147b push edi
0041147c push ecx
0041147d lea edi,[ebp-0cch]
00411483 mov ecx,33h
00411488 mov eax,0cccccccch
0041148d rep stos dword ptr es:[edi]
0041148f pop ecx
00411490 mov dword ptr [ebp-8],ecx
00411493 mov ecx,dword ptr [this]
00411496 call student::student (411109h)
可以看到在執行m_inum = 1前先呼叫了父類的建構函式。
再來看看析構時的順序(教科書上寫的是先呼叫子類的析構函式,在呼叫父類的,與構造過程相反)
~gradeonestue()
0041157d mov ecx,dword ptr [this]
00411580 call student::~student (41102dh)
可以看到順序和教科書上一樣。
二、析構函式是virtual,正常定義乙個子類物件
建構函式順序就略過了,看析構彙編**
virtual ~gradeonestue()
00411706 mov ecx,dword ptr [this]
00411709 call student::~student (411091h) ...
可以看到,析構函式最後還是呼叫了父類的析構(即使是虛函式)。
三、用乙個父類指標去new乙個子類物件,析構函式不是virtual,構造過程略過
class student
~student()
};
class gradeonestue:public student
~gradeonestue()
};
int _tmain(int argc, _tchar* argv)
看delete pstu處的彙編**
delete pstu;
00413726 mov eax,dword ptr [ebp-14h]
00413729 mov dword ptr [ebp-0e0h],eax
0041372f mov ecx,dword ptr [ebp-0e0h]
00413735 mov dword ptr [ebp-0ech],ecx
0041373b cmp dword ptr [ebp-0ech],0
00413742 je wmain+0c9h (413759h)
00413744 push 1
00413746 mov ecx,dword ptr [ebp-0ech]
0041374c call student::`scalar deleting destructor' (4111e5h)
00413751 mov dword ptr [ebp-10ch],eax
00413757 jmp wmain+0d3h (413763h)
00413759 mov dword ptr [ebp-10ch],0
return 0;
看到只呼叫了父類的析構函式,此時子類的析構函式沒有被呼叫,此時子類的析構函式中雖然有呼叫父類析構函式的**,但是這裡直接呼叫的是父類的析構函式,所以這是如果子類中析構函式有釋放資源的**,這裡會造成這部分資源不被釋放,有可能造成記憶體洩露
四、用乙個父類指標去new乙個子類物件,析構函式是virtual,構造過程略過
這裡直接看delete pstu的彙編**
delete pstu;
004114e6 mov eax,dword ptr [ebp-14h]
004114e9 mov dword ptr [ebp-0e0h],eax
004114ef mov ecx,dword ptr [ebp-0e0h]
004114f5 mov dword ptr [ebp-0ech],ecx
004114fb cmp dword ptr [ebp-0ech],0
00411502 je wmain+0d9h (411529h)
00411504 mov esi,esp
00411506 push 1
00411508 mov edx,dword ptr [ebp-0ech] //edx等於pstu,指向new出來的子類物件
0041150e mov eax,dword ptr [edx] //將edx指向的dword傳給eax,eax現在是儲存的虛函式表指向的位址
00411510 mov ecx,dword ptr [ebp-0ech]
00411516 mov edx,dword ptr [eax] //將虛函式表指向的第乙個函式的位址傳給edx,也就是唯一的乙個虛析構函式的位址
00411518 call edx //呼叫析構函式,這個函式是子類的,這個位址構造時寫入虛函式表
由於子類的虛構函式最後會呼叫父類的析構函式(不管是否為virtual,子類析構函式最後都會呼叫父類析構函式),所以最終父類的析構函式會得到執行。(這時呼叫的析構函式相當於pstu->vptable->析構函式(),這個析構函式是構造時寫入的,由於構造時使用子類型別去new,此時虛函式表中析構函式的位址是子類的析構函式位址)
最後的結論:
1、無論父類與子類的析構函式是否是virtual,子類的析構函式都會呼叫父類的析構函式
2、如果父類與子類的析構函式不為virtual,用乙個父類指標指向乙個用子類型別new的物件,delete時,直接呼叫父類的析構函式,這是在編譯時刻就決定的。如果子類析構函式中有釋放資源的**,這是會發生資源洩漏。
3、如果父類與子類的析構函式是virtual,用乙個父類指標指向乙個用子類型別new的物件,delete時,這時由於是通過虛函式表呼叫析構函式,而虛函式表中的位址是構造時寫入的,是子類的析構函式的位址,由於結論第一條,所以子類與父類的析構函式都會得到呼叫,不會發生資源洩漏。
**:
C 中析構函式為虛函式
1 析構函式定義為虛函式時 基類指標可以指向派生類的物件 多型性 如果刪除該指標delete p 就會呼叫該指標指向的派生類析構函式,而派生類的析構函式又自動呼叫基類的析構函式,這樣整個派生類的物件完全被釋放。2 析構函式不定義為虛函式時 編譯器實施靜態繫結,在刪除基類指標時,只會呼叫基類的析構函式...
C 析構函式 虛析構函式
1.為什麼要定義虛析構函式?如果有乙個帶有虛函式功能的類,則它需要乙個虛析構函式,原因如下 1 如果乙個類有虛函式功能,它經常作為乙個基類使用 2 如果它是乙個基類,它的派生類經常使用new來分配 3 如果乙個派生類物件使用new來分配,並且通過乙個指向它的基類的指標來控制,那麼它經常通過乙個指向它...
C 虛析構函式 純虛析構函式
虛析構函式 析構函式的工作方式是 最底層的派生類 most derived class 的析構函式最先被呼叫,然後呼叫每乙個基類的析構函式。因為在c 中,當乙個派生類物件通過使用乙個基類指標刪除,而這個基類有乙個非虛的析構函式,則結果是未定義的。執行時比較有代表性的後果是物件的派生部分不會被銷毀。然...