工作問題積累(二十)銷燬windows物件時,使用DestroyWindow而不是delete C++
1.問題
做客戶端程式,避免不了與視窗類打交道,昨天就遇到了一個記憶體釋放的問題。程式碼如下:
class CFriendButton:public CBFCWnd { public: CFriendButton(); virtual ~CFriendButton(); protected: afx_msg void OnLButtonDown(UINT nFlags, CPoint point); afx_msg void OnSize(UINT nType, int cx, int cy); DECLARE_MESSAGE_MAP() public: void SelectBtn( int nTabPage,BOOL bshow ); void SetButtonTitle(CString strTitle); public: int m_nButtonID; COLORREF m_colorHover; COLORREF m_colorSelected; };
上面是一個類的定義,最底層的基類還是CWnd,具體函式實現就不寫出來了。
vector<CFriendButton *> m_vetButton 每次new 之後呼叫m_vetButton.push_back()該指標, 在解構函式中執行如下程式碼: for (vector<CRoomButton *>::iterator it = m_vetButton.begin(); it != m_vetButton.end(); it ++) { if (NULL != *it) { delete *it; *it = NULL; } } m_vetButton.clear();
直接關閉主視窗時,程式碼就會報錯,這個vector中的指標究竟怎麼來釋放呢?
2.解析
Destroy Window Objects(銷燬視窗物件)如標題所訴,“要銷燬一個C++的Windows物件,使用DestroyWindow而不是delete”這個是最重要的規則。
如果你遵照以下的指導方法,你就會很少碰到清除方面的問題(例如忘記刪除/釋放C++記憶體,忘記釋放系統資源比如說HWNDS,或者釋放物件太多次),我們必須提供一組規則以防止系統資源或者應用程式的記憶體洩露問題,同時也防止物件和windows控制代碼被多次銷燬。
銷燬視窗:
有兩種方法被允許來銷燬一個windows物件:
(1.)呼叫CWnd::DestroyWindow或者Windows API::DestroyWindow.
(2.)利用delete操作符來進行明確的刪除操作。
第一種方法是迄今為止最常用的。即使DestroyWindow沒有在你的程式碼裡被直接呼叫,此方法也照常適用。這種情況就是,當用戶直接關閉一個框架視窗時(預設的WM_CLOSE行為主動呼叫DestroyWindow),當一個父視窗(框架視窗)被銷燬時,windows會呼叫DestroyWindow來銷燬它的所有的子視窗。
利用CWnd::PostNcDestroy進行自動清除,當銷燬一個windows視窗時,最後傳送給此視窗的windows訊息是WM_NCDESTROY。CWnd對此訊息的預設處理(CWnd::OnNcDestroy)會將C++物件與HWND分離,並呼叫虛擬函式PostNcDestroy。一些類過載這個函式來刪除C++物件。
CWnd::PostNcDestroy的預設操作是什麼也不做,這適合於那些分配在堆疊或者嵌在其他物件裡面的視窗物件。這不適用於那些分配在堆上的視窗物件(不嵌在其他C++物件中)。
那些設計來分配在堆上的類可以過載成員函式PostNcDestroy以執行“delete this”操作。它將會釋放任何與此C++物件相關的C++記憶體。儘管預設的CWnd解構函式會在m_hwnd不為空的情況下呼叫DestroyWindow,但這不會導致無窮遞迴,因為此控制代碼在清除階段將會處於分離狀態併為空。
void CBFCWnd::PostNcDestroy()
{
TRACE(_T("PostNcDestroy\r\n"));
if (m_pWndManager)
{
m_pWndManager->DeletedWnd(this);
m_pWndManager = NULL;
}
if (m_nAutoDelete)
delete this;
//將實體記憶體的佔用挪到虛擬記憶體裡
::SetProcessWorkingSetSize(::GetCurrentProcess(), -1, -1);
}
注意:CWnd::PostNcDestroy一般會在windows訊息WM_NCDESTROY處理後被呼叫,把它作為視窗銷燬的一部分,同時HWND和C++視窗物件不再關聯。
如果你直接使用操作符delete,MFC的診斷記憶體分配算符將會警告你:你正在第二次釋放記憶體(第一次呼叫delete,還有在PostNcDestroy的自動清理中執行過程中呼叫delete this)。
for (int i=0;i<m_vetButton.size();++i)
{
delete m_vetButton[i];
}