派生元クラスでの this の扱いについて

解決


neoge  2004-07-23 22:56:00  No: 54078

class A {
    virtual void Delete() {
        delete this;
    }
};
class B : public A {
    void Delete() {
        delete this;
    }
};
class C : public A {
    void Delete() {
        delete this;
    }
};

int nain() {
    A* pData[3] = {
        new A,
        (A*)new B,
        (A*)new C
    };
    for (int i=0; i < 3; i++) {
        A[i]->Delete();
    }
    return 0;
}

上記のようなソースがあるとして教えてください。
class B, class C の
  void Delete()
関数についてですが、これは絶対に必要でしょうか?
自分はこのような場合は class B, class C には void Delete() を
実装せず、基本クラス(A)で実装している void Delete() を呼んでいました。

ただ、
「class A で実装している void Delete() 関数内の
    delete this;
  の this は A* 型 だから class B, class C から呼ぶと問題あるのでは?」
という意見を最近聞きました。

実際のところはどうなのでしょうか?
意見をくれた人も自信が無いとの事ではっきりした答えがでていません。
よろしくお願いします。

VC++6.0+SP5


tetrapod  2004-07-23 23:43:28  No: 54079

A::~A が virtual であるなら A::Delete() { delete this; } だけでOK。
A::Delete は virtual である必要すらありません。
提示の例題では A::~A が virtual でないので、派生クラス側に virtual な Delete() が必要です。
破棄の際に派生クラス側のデストラクタを起動する必要がありますから。
IS 5.3.5 Delete 2, 3


neoge  2004-07-24 00:11:12  No: 54080

デストラクタが重要だったのですね。

tetrapod さんのおかげで今回の質問の答えだけでなく、
デストラクタは取り合えず virtual にしておけ、と言われる事の意味も
(全てでは無いかもしれませんが)理解する事ができました。
ありがとうございました。


tetrapod  2004-07-24 00:34:24  No: 54081

解決マークがつきましたが、蛇足。

delete this; とするから難しくみえるのであって、ふつーに考えればいいのです。

struct drawable { ...
 virtual ~drawable();
 virtual void Draw()=0;
};
struct rectangle : public drawable { ... };
struct circle : public drawable { ... };
void DrawIt(drawable* p) { p->Draw(); }
void RemoveIt(drawable* p) { delete p; }
なんてコードはごく普通に使うと思うのですが、この delete p; で
正しく派生クラス (rectangle, circle) のインスタンスが処分されるためには
基底クラス (drawable) のデストラクタが virtual である必要があります。

delete p; と delete this; ではインスタンスが破棄される経路自体には大差ありません。
# delete this; の後に(暗黙にも)メンバ変数・関数(vptr/vtbl) を触るとバグります。
# が、まあそれは delete p; でも同じこと。

提示のコードでは most-derived-class のデストラクタを起動するためのロジックが
・デストラクタ自身にある
・Delete() 関数にある
か、の違いということになります。
常に派生して使うのであればデストラクタが virtual なほうが使いやすいでしょう。


neoge  2004-07-24 04:26:05  No: 54082

tetrapodさん、レスありがとうございます。
それでレスを見ていて気付いたのですが、自分はかなり変な思い込みをしていたようです。

  drawable* p = (drawable*)new rectangle;
  delete p;

私はこのコードは
  delete (rectangle*)p;
にしないと問題があると考えていました。
それも rectangle::~rectangle() を呼び出すため云々ではなく、 new で確保した際の型を
delete にきちんと知らせないとメモリリークが発生する(デストラクタで後始末ができ
ないから、ではなく単純に new したメモリを開放しきれずに…)、というワケの解らない
思い込みからでした。

で、今回の質問も tetrapod さんが問題にされている"デストラクタを起動"という点ではなく、
delete に正確な型を知らせないとメモリリークが発生するのでは という考えからしました。

> デストラクタは取り合えず virtual にしておけ、と言われる事の意味も理解…
デストラクタが呼ばれるという事は当然 delete もその型を知っている事になる…
という事はメモリリークも…、これが virtual にしておけという…云々…
とんでもない勘違いでした。恥ずかしくなります。

> IS 5.3.5 Delete 2, 3
ISO C++ で定められている事に関する知識が無くてもそこそこやっていける事と怠慢から
知識を得る事を避けていたのですが、今回の事を機に目を通したいと思います。

ありがとうございました。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加