Deleteによる破棄に関して

[戻る]
JINJ 2011/05/18(水) 17:53:50
かなり初歩的な質問かもしれませんが、確信が得られないので教えてください。

本体で他のCppで作成したDialogをnewします。
CDialog *m_Dlg1;
m_Dlg1 = new CDlg();

このnewしたm_Dlg1を破棄したいのですが、実際に本体側で
delete m_Dlg1;
として破棄する方法はわかるのですが、
CDlg側で破棄する場合
CDlg::Destroy()
{
  delete this;
}
とし、Destroy関数を呼んだらm_Dlg1は破棄されますか?
クラスとポインタがあいまいなため自信がありません。
変な質問だったらすいません。

Visual Studio 2005
Windows XP
MFC

επιστημη [E-Mail] [HomePage] 2011/05/18(水) 18:36:25
> CDlg::Destroy()
> {
>  delete this;
> }
> とし、Destroy関数を呼んだらm_Dlg1は破棄されますか?

YES.

> クラスとポインタがあいまいなため自信がありません。

なにが「あいまい」なのでしょうか?

仲澤@失業者 2011/05/18(水) 19:00:29
自分の発言は、重箱の隅的補足です。

1.MFCを使っている場合。当該のクラスが外部DLLに実装されている場合で、
 exe側で new したものを、DLL内で delete するのは×。その逆も×。

なので、注意しましょう。
掲載されたコードは、そうなりやすい特徴を持ってますね。

PATIO 2011/05/18(水) 23:40:30
PATIO死ね

JINJ 2011/05/19(木) 08:53:20
回答ありがとうございます。

επιστημηさん
あいまいだったのは、本体でNewしたものとCDlg内でのthisが違うインスタンス?
になってしまうのかどうかのところがあいまいだったのでそう書きました。
本体側で
delete m_Dlg1;
としなければいけないものだと思っているので。

すいません、まだ勉強不足でよくわかっていないです。


m_Dlg1 = new CDlg();
m_Dlg2 = new CDlg();

とした場合はm_Dlg1とm_Dlg2は違うものになり、2つ生成されますよね?
これを
CDlg::Destroy()
{
  delete this;
}

とした場合は?


仲澤@失業者さん
DLL←→EXEで注意するとのことで、プロジェクトが同じものの中で使用する分には問題
ないですよね。現状同一プロジェクト上で考えています。

ryo 2011/05/19(木) 09:07:07
m_Dlg1->this
m_Dlg2->this

この2つのthisは、同じになるのか?
上のことを踏まえて

m_Dlg1->OnDestory() ・・・>this
m_Dlg2->OnDestory() ・・・>this

は、どうなるか?
順番に考えればわかるはず

*実際に動かすソースコードではなく、ニュアンスだけです

仲澤@失業者 2011/05/19(木) 11:35:37
>DLL←→EXEで注意するとのことで、プロジェクトが同じものの中で使用する分には問題
>ないですよね。現状同一プロジェクト上で考えています。

です。
さて、厳密さを無視すると次の要点が理解を助けるかもしれません。

1.「クラス」は型の定義で、その型の所有物と振る舞いを定義したもの。
2.「クラス」を実体化したのが「インスタンス」。
3.ある型の「インスタンス」を作成するには、その型のコンストラクタを
 実行する必要がある。
4.コンストラクタが実行されるたびに、新しい別のインスタンスが生成される。
5.「this」は、この実体化された別々インスタンスの自身を意味するもので、
 クラスではない(ここを勘違いする人が多いと思う)。
6.簡略にはthisは型から実体化されたインスタンスのポインタ(アドレス)だと
 認識しておけばOK。

hirocco 2011/05/19(木) 11:45:42
CDlg内に記述してあるのは「お品書き」というか「レシピ」というか、
振る舞いを定義してあるというだけでって感じかなぁ
たとえばAさんとBさんが同じレシピ本に書いてある"肉ジャガ"を作ったとして
本に「出来上がったコレ(this)をお皿に盛り付けます」
と書いてあっても,Aさんの"肉ジャガ"とBさんの"肉ジャガ"は別のもの…

あ,全然例えが間違ってたかも…www

あと,注意点として
CDlg::Destroy()
{
  delete this;
}
で破棄した後でも,
m_Dlg1の内容はNULLではなく破棄されたポインタが入ったままなので,
不用意にアクセスしたら,怒られちゃうかもね

tetrapod 2011/05/19(木) 12:08:18
OnDestroy (イベントハンドラ) ではなく Destroy (自作破棄関数) のようだけど。

破棄されるか否か、という点では問題なく破棄される。
ただ、破棄後 (Destroy 呼び出し後) に当該 CDlg インスタンスに対する
アクセスは一切できなくなってしまうという点には注意が必要。
・ダイアログを開いたまま Destroy できない。
・この Destroy はダイアログを閉じるの意味で使うことはできない。

破棄後の CDlg インスタンスに明示・暗示アクセスがないよう工夫しなけばならない。
・Destroy 後にダイアログデータにアクセスすることはできない。
・MFC の内部に隠れているメッセージループ処理のハンドラなどから
 暗黙にアクセスがあったりしないか?
・同様 DDX 系処理から暗黙にアクセスが無いか?
などなど不安事項はいくつも残りそう。俺としてはお勧めしない。

Modal ならば new->delete する必然は無いはずなので Modeless なのだろうが
そもそもそういうことをする必然について再考察してみたほうが良いかも。
最初から delete this; する設計のクラスであればよいのだが
CDialog はそういう設計にはなっていないわけだし。

ryo 2011/05/19(木) 12:18:51
'On'Destroyって書いてしまったのは、私のミスです。
ごめんなさい

JINJ 2011/05/19(木) 15:01:18
みなさん回答ありがとうございます。
hirocoさんの言っている
> m_Dlg1の内容はNULLではなく破棄されたポインタが入ったままなので,
の文章を見て、私の疑問に思っていることがなんとなく理解できた気がします。
またほかのみなさんの意見もとても参考になります。

結果本体側でnewしたものを
delete this;
しておけば、メモリリークは防げるが、結局m_Dlg1には破棄されたポインタが入ってい
て処理を行うことができない。
ただ、どちらにしろ
delete m_Dlg1;
と本体側でしてもm_Dlg1にアクセスができなくなってしまうと思いますので、
(if m_Dlg1<>NULL とするべきでしょうが…)
再度newする必要が出てくるのですよね。

私のしたかったことは単にOpenしたときにCDlgのインスタンスを作成して、閉じたとき
に破棄したいということをしたいのです。
Cdlg内で
CDialog *m_Dlg1
を宣言しておき、Oncreate、Ondestroyで
m_Dlg1を生成、破棄すればいいのかな。
そのm_Dlg1を本体側でNULLでなければ使用するという形をとるのがよさそうでしょう
か?

よろしくお願いします。

hirocco 2011/05/19(木) 15:49:34
でも,ワタシ的にはDestroyで破棄ってあんまり好きじゃないんですよねぇ
メインの初期化部分でnewだけして後はイベント待機だけで,
newした時のポインタを保持っておく必要がなければ
メイン終了時にdeleteするためだけにポインタを保持っておくのはメンドクサイので,
自分で破棄してくれると便利だねぇって思いますよ

ワタシがモードレスを破棄させる時によくするのは,普通のダイアログとかだったら不要
になった時点をそのダイアログに判別させて,そのダイアログがメインに"破棄してく
れ"ってメッセージをメインに投げて,メイン側で破棄してしまう.
これならそのタイミングでメインがm_Dlg1をNULLに再初期化できるでしょ

あ,それかこんなのどう?
newするのは1回だけdeleteするのも一回だけ
たぶんコンストラクタとデストラクタかな?
で、ダイアログが必要なときにだけ
GetSafeHwndで調べてNULLだったらCreateしてShowWindowする
でClose時(OnOK/OnCancel含)にダイアログ側でDestroyWindowする
それで,また必要になったら
GetSafeHwndで調べてからNULLだったらCreateしてShowWindowする
NULLじゃなければCreateは不要だね
その他メソッドを呼ぶときも
m_Dlg1->GetSafeHwndで安全かどうかを調べてから呼ぶ

どうかな?

JINJ 2011/05/19(木) 16:16:33
[[解決]]
hiroccoさん
アドバイスありがとうございます。
とにかくコンストラクタ、デストラクタのみで生成、破棄を行い、変なところで生成す
るのはやめときます。

みなさん
勉強になりました、ありがとうございました。

gak 2011/05/19(木) 18:39:04
> m_Dlg1を本体側でNULLでなければ使用するという形をとるのがよさそうでしょうか?
本体側って奴からm_Dlg1を触る必要があるのならその本体側でm_Dlg1を持てば?と思う。

「作った後はほったらかし。終了時には自分で死んでくれ」という使い方ならば

class CDlg : public CDialog {
    bool autoDelete_;
    
    CDlg::CDlg() : autoDelete_(false) {}
    static bool ShowModeless(CWnd* parent) {
//      if (parent != NULL) {
//        DWORD pid;
//          ::GetWindowThreadProcessId(parent->GetSafeHwnd(), &pid);
//          ASSERT(pid == GetCurrentProcessId());
//      }
        CDlg *dlg = new CDlg;
        if (dlg->Create(IDD, parent)) {
            dlg->autoDelete_ = true;
            dlg->ShowWindow(SW_SHOW);
        }
    }
    virtual void PostNcDestory() {
        CDialog::PostNcDestory();
        if (autoDelete_) {
            delete this;
        }
    }
};

本体側::xxxx(CWnd *parent) {
    for (int i=0; i < 欲しい数だけ; ++i) {
        CDlg::ShowModeless(parent);
    }
}

て感じにインスタンスの管理すら放棄して、PostNcDestory(http://msdn.microsoft.com
/ja-jp/library/sk933a19.aspx)で「delete this」して勝手に消える様にしてやれば良い。

> delete this; しておけば、メモリリークは防げるが、
メモリリークを防ぐのを最大の目的として「delete this」したいと言うのであれば
「ちょっと違うかなぁ」と個人的には思った。

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