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