親クラスと子クラスの問題について

[戻る]
困ってます 2015/09/09(水) 09:01:33
Aというクラスがあり、その中でBというクラスを起動し、そして閉じる、という処理を
行っています。
Aの中身はこんな感じ↓です。

B    *m_b;

void A::B_Kidou()
{
    m_b = new B();
}

void A::B_Tojiru()
{
    m_b->DestroyWindow();
    delete m_b;
}


クラスBはダイアログです。
クラスBの起動と閉じる処理は、基本的にクラスAが制御します。
しかしBの中で、クラスAのB_Tojiruを呼ぶこともあります。これって問題ありますか?

呼んだあとその関数はすぐに終わるのですが、もしその後に無限ループとか行っていた
ら、それってどうなるんだろう?というのも気になります。
よろしくお願いします。

仲澤@失業者 2015/09/09(水) 10:57:11
MFCであるということを前提にすると、
まず、CWndの派生クラスは、

 1.そのクラスのインスタンスの作成
 2.インスタンスからウインドウの作成

の二段階で生成します。
消滅は

 3.ウインドウの破棄
 4.インスタンスの破棄

の順になります。ただし、

 5.ウインドウの破棄はそのHWNDを知る全ての場所から可能

なのに比べて、

 6.インスタンスの破棄は、生成者しかできない

ということになっているので、一般に、モードなしのウインドウについては
ウインドウの破棄とインスタンスの破棄を同期するのは結構難しいわけですね。
以上を勘案すると、

 7.誰からでもDestroyWindow()される可能性のあるCWndの派生クラスの
  インスタンスは、その所有者の寿命と同じであることが望ましく、
  当該クラスのHWNDの有効性に同期させるべきではない。

ということがわかります。
従って、class Bは newしないでAに保持させれば、何があっても
問題ありませんし、BからむりやりA::B_Tojiru()を呼ばなくても
Bの内部で自身のDestroyWindow()を呼ぶだけでよいということになります。

困ってます 2015/09/11(金) 11:07:53
回答ありがとうございます。
B自身でDestroyWindowをやった後、そのインスタンスの破棄も行いたいのです。
しかしインスタンスの破棄は、Aでしか行えませんよね?
それを行うのがAクラスのB_Tojiruなんです。

Bクラスが自分自身で破棄するにしても、そのことをAに通知してB_Tojiruを呼ぶ、という
処理が必要になります。
だったらBからB_Tojiruを呼んだほうが簡単なのですが、それは問題あるか?というのが
質問趣旨です。

それともうひとつ。
7.の「当該クラス」とは、この場合、Bという認識でOKでしょうか?

仲澤@失業者 2015/09/11(金) 11:35:09
>7.の「当該クラス」とは、この場合、Bという認識でOKでしょうか?

はい。その通りです。

前の自分の発言の通り、
「BクラスのHWNDの有効性」と
「Bクラスのインスタンスの寿命」
を同期させるのは、一般的には困難なのであまりお勧めできません。

関係を下記の様に仮定すると
class B
{
   A *   m_Parent;
};

B::EndMyself() // 自身の終了
{
  m_Parent->B_Tojiru();
}

本ケースのコードの場合で、最悪の場合の実装を想定すると、次のような動作になります
。

(1)m_Parent->B_Tojiru();を実行
(2)A::B_Tojiru()で、m_b->DestroyWindow();を実行
(3)BクラスにWM_DESTROY、WM_QUERYENDSESSION、WM_ENDSESSION、WM_NCDESTROY
   等の終了用のメッセージが来ます。
  全体の実装方法にもよりますが、
  Bのインスタンスは、まだEndMyself()内でm_Parent->B_Tojiru()を実行中の
  場合がありえます。
(4)delete m_b; によりB::~B()が実行されインスタンスが破棄されます。
(5)m_Parent->B_Tojiru()から復帰しましたが、既にBのインスタンスは破棄されてます。

さて、一般的な場合にはWM_DESTROYが来る前にm_Parent->B_Tojiru()から復帰しますが、
それでも自身のインスタンスは破棄されています。

このような実装が現実的でないことはもうお分かりだと思います。

最後に、これ(親に自分を破棄させる)を実現する他の方法を考えると、

(a)ユーザーがBの終了を選んだ。
(b)Bは親のAに対して、PostMessage()でそのことを知らせる。
(c)AはメッセージでユーザーがBの終了を選択したことを知る。
(d)AはB::DestroWindow()を実行し、インスタンスを破棄する。

この方法ならぶっ飛びませんよね。

古いスレッドには追加発言できません。