このメモでは、 CWnd::PostNcDestroy
メソッドの使用方法について説明します。
CWnd
派生オブジェクトの割り当てをカスタマイズする場合は、このメソッドを使用します。 また、delete
演算子ではなく、CWnd::DestroyWindow
を使用して C++ Windows オブジェクトを破棄する必要がある理由についても説明します。
この記事のガイドラインに従うと、クリーンアップの問題はほとんどありません。 これらの問題は、C++ メモリの削除/解放の忘れ、 HWND
などのシステム リソースの解放の忘れ、オブジェクトの解放回数が多すぎるなどの問題が原因で発生する可能性があります。
問題
各 windows オブジェクト ( CWnd
から派生したクラスのオブジェクト) は、C++ オブジェクトと HWND
の両方を表します。 C++ オブジェクトはアプリケーションのヒープに割り当てられ、 HWND
はウィンドウ マネージャーによってシステム リソースに割り当てられます。 ウィンドウ オブジェクトを破棄するにはいくつかの方法があるため、システム リソースまたはメモリ リークを防ぐ一連のルールを提供する必要があります。 これらの規則では、オブジェクトと Windows ハンドルが複数回破棄されないようにする必要もあります。
ウィンドウの破棄
Windows オブジェクトを破棄するには、次の 2 つの方法を使用できます。
CWnd::DestroyWindow
または Windows APIDestroyWindow
の呼び出し。delete
演算子を使用して明示的に削除する。
最初のケースが最も一般的です。 このケースは、コードが DestroyWindow
を直接呼び出さない場合でも適用されます。 ユーザーがフレーム ウィンドウを直接閉じると、このアクションによってWM_CLOSE メッセージが生成され、このメッセージに対する既定の応答は DestroyWindow
を呼び出します。 親ウィンドウが破棄されると、Windows はすべての子に対して DestroyWindow
を呼び出します。
2 番目のケースは、Windows オブジェクトで delete
演算子を使用することはまれです。
delete
を使用することが正しい選択である場合を次に示します。
自動クリーンアップ CWnd::PostNcDestroy
システムが Windows ウィンドウを破棄すると、ウィンドウに送信された最後の Windows メッセージは WM_NCDESTROY
。 そのメッセージの既定の CWnd
ハンドラーは CWnd::OnNcDestroy
。
OnNcDestroy
は、C++ オブジェクトから HWND
をデタッチし、仮想関数 PostNcDestroy
を呼び出します。 一部のクラスでは、この関数をオーバーライドして C++ オブジェクトを削除します。
CWnd::PostNcDestroy
の既定の実装では何も行われません。これは、スタック フレームに割り当てられているウィンドウ オブジェクトや他のオブジェクトに埋め込まれているウィンドウ オブジェクトに適しています。 この動作は、他のオブジェクトのないヒープ上の割り当て用に設計されたウィンドウ オブジェクトには適していません。 つまり、他の C++ オブジェクトに埋め込まれていないウィンドウ オブジェクトには適していません。
ヒープでの割り当て専用に設計されたクラスは、delete this;
を実行するためにPostNcDestroy
メソッドをオーバーライドします。 このステートメントは、C++ オブジェクトに関連付けられているすべてのメモリを解放します。 既定のCWnd
デストラクターの呼び出しDestroyWindow
m_hWnd
NULL
されていない場合でも、この呼び出しは、クリーンアップ フェーズ中にハンドルがデタッチされ、NULL
されるため、無限再帰につながりません。
注
通常、システムは Windows WM_NCDESTROY
メッセージを処理した後にCWnd::PostNcDestroy
を呼び出し、HWND
と C++ ウィンドウ オブジェクトは接続されなくなりました。 障害が発生した場合、システムはほとんどのCWnd::Create
呼び出しの実装でCWnd::PostNcDestroy
を呼び出します。 自動クリーンアップ規則については、この記事の後半で説明します。
自動クリーンアップ クラス
次のクラスは、自動クリーンアップ用に設計されていません。 通常、これらは他の C++ オブジェクトまたはスタックに埋め込まれます。
すべての標準 Windows コントロール (
CStatic
、CEdit
、CListBox
など)。CWnd
から直接派生した子ウィンドウ (カスタム コントロールなど)。分割ウィンドウ (
CSplitterWnd
)。既定のコントロール バー (
CControlBar
から派生したクラス。コントロール バー オブジェクトの自動削除を有効にする 場合のテクニカル ノート 31 を参照してください)。スタック フレーム上のモーダル ダイアログ用に設計されたダイアログ (
CDialog
)。CFindReplaceDialog
を除くすべての標準ダイアログ。ClassWizard によって作成された既定のダイアログ。
次のクラスは、自動クリーンアップ用に設計されています。 通常、これらはヒープ上で自分で割り当てられます。
メイン フレーム ウィンドウ (
CFrameWnd
から直接または間接的に派生)。ウィンドウの表示 (
CView
から直接または間接的に派生)。
これらの規則を解除する場合は、派生クラスの PostNcDestroy
メソッドをオーバーライドする必要があります。 クラスに自動クリーンアップを追加するには、基底クラスを呼び出し、 delete this;
を実行します。 クラスから自動クリーンアップを削除するには、直接基底クラスのPostNcDestroy
メソッドではなく、CWnd::PostNcDestroy
を直接呼び出します。
自動クリーンアップ動作を変更する最も一般的な用途は、ヒープに割り当てることができるモードレス ダイアログを作成することです。
呼び出すタイミング delete
DestroyWindow
を呼び出して、C++ メソッドまたはグローバル DestroyWindow
API のいずれかの Windows オブジェクトを破棄することをお勧めします。
MDI 子ウィンドウを破棄するためにグローバル DestroyWindow
API を呼び出さないでください。 代わりに仮想メソッド CWnd::DestroyWindow
を使用する必要があります。
自動クリーンアップを実行しない C++ Window オブジェクトの場合、delete
演算子を使用すると、VTBL
が正しく派生したクラスを指していない場合、CWnd::~CWnd
デストラクターでDestroyWindow
を呼び出そうとすると、メモリ リークが発生する可能性があります。 リークは、システムが呼び出す適切な destroy メソッドを見つけることができないために発生します。
delete
の代わりにDestroyWindow
を使用すると、これらの問題を回避できます。 このエラーは微妙な場合があるため、デバッグ モードでコンパイルすると、リスクがある場合は次の警告が生成されます。
Warning: calling DestroyWindow in CWnd::~CWnd
OnDestroy or PostNcDestroy in derived class will not be called
自動クリーンアップを実行する C++ Windows オブジェクトの場合は、 DestroyWindow
を呼び出す必要があります。
delete
演算子を直接使用すると、MFC 診断メモリ アロケーターから、メモリを 2 回解放すると通知されます。 2 つの出現箇所は、最初の明示的な呼び出しと、PostNcDestroy
の自動クリーンアップ実装でdelete this;
する間接呼び出しです。
自動クリーンアップ以外のオブジェクトに対して DestroyWindow
を呼び出した後も、C++ オブジェクトはそのまま残りますが、 m_hWnd
は NULL
。 自動クリーンアップ オブジェクトで DestroyWindow
を呼び出すと、 PostNcDestroy
の自動クリーンアップ実装で C++ delete 演算子によって C++ オブジェクトが解放されます。