24

過去 4 年間 C# に携わってきたので、C++ の現在のベスト プラクティスと一般的な設計パターンに興味があります。次の部分的な例を考えてみましょう。

class World
{
public:
    void Add(Object *object);
    void Remove(Object *object);
    void Update();
}

class Fire : Object
{
public:
    virtual void Update()
    {
        if(age > burnTime)
        {
            world.Remove(this);
            delete this;
        }
    }
}

ここでは、一連のオブジェクトを管理し、それらを定期的に更新する責任を負う世界があります。火は、さまざまな状況下でワールドに追加される可能性のあるオブジェクトですが、通常は既にワールドにある別のオブジェクトによって追加されます。火はいつ燃え尽きたかを知る唯一のオブジェクトなので、現在はそれ自体を削除しています。火災を引き起こしたオブジェクトは、おそらく存在しないか、関連性がありません。

これは賢明なことですか、それともこれらのオブジェクトをクリーンアップするのに役立つより良い設計はありますか?

4

11 に答える 11

30

仮想関数を作成Updateしました。これは、派生クラスが の実装をオーバーライドする可能性があることを示唆していますUpdate。これにより、2 つの大きなリスクが生じます。

1.) オーバーライドされた実装は、 を実行することを覚えているかもしれませんが、 .World.Removeを忘れているかもしれませんdelete this。メモリがリークされています。

2.) オーバーライドされた実装は base-classUpdateを呼び出します。この base-class は を実行しますdelete thisが、さらに処理を進めますが、無効な this-pointer を使用します。

次の例を検討してください。

class WizardsFire: public Fire
{
public:
    virtual void Update()
    {
        Fire::Update();  // May delete the this-object!
        RegenerateMana(this.ManaCost); // this is now invaild!  GPF!
    }
}
于 2009-02-06T23:42:04.357 に答える
20

これの問題は、オブジェクトと World クラスの間に暗黙の結合を実際に作成していることです。

World クラスの外で Update() を呼び出そうとするとどうなりますか? オブジェクトが削除される可能性がありますが、その理由はわかりません。責任がひどく混同されているようです。これは、このコードを書いたときには考えもしなかった新しい状況で Fire クラスを使用した瞬間に問題を引き起こします。オブジェクトを複数の場所から削除する必要がある場合はどうなりますか? おそらく、世界、現在のマップ、およびプレイヤーのインベントリの両方から削除する必要がありますか? 更新機能はそれを世界から削除し、次にオブジェクトを削除します。次にマップまたはインベントリがオブジェクトにアクセスしようとすると、悪いことが起こります。

一般に、Update() 関数が更新中のオブジェクトを削除するのは非常に直感的ではないと思います。また、オブジェクトがそれ自体を削除するのは直感的ではないと思います。オブジェクトは、燃焼が終了したことを知らせるイベントを発生させる何らかの方法を持っている可能性が高く、興味のある人は誰でもそれに基づいて行動できます。たとえば、世界からそれを削除します。それを削除するには、所有権の観点から考えてください。

オブジェクトの所有者は誰ですか? 世界?つまり、オブジェクトがいつ死ぬかは世界だけが決めるということです。オブジェクトへの世界の参照がそれへの他の参照よりも長持ちする限り、それは問題ありません。オブジェクトはそれ自体を所有していると思いますか? それは一体何の意味ですか?オブジェクトが存在しなくなったらオブジェクトを削除する必要がありますか? 意味がありません。

ただし、単一の所有者が明確に定義されていない場合は、共有所有権を実装します。たとえば、次のような参照カウントを実装するスマート ポインターを使用します。boost::shared_ptr

ただし、オブジェクト自体にメンバー関数があり、オブジェクトが特定のリストから削除され、そこに存在するかどうか、および他のリストにも存在するかどうかにかかわらず、オブジェクト自体も削除されます。それへの参照は存在しますが、悪い考えです。

于 2009-02-06T23:49:52.297 に答える
6

オブジェクトが割り当てられておらず、new で構築されていない場合、削除は失敗し、プログラムがクラッシュする可能性があります。この構成により、スタック上または静的にクラスをインスタンス化できなくなります。あなたの場合、ある種の割り当てスキームを使用しているようです。これさえ守れば、安全かもしれません。私なら絶対にしないですけどね。

于 2009-02-06T23:39:24.687 に答える
5

newの呼び出し元がUpdateその動作について適切に通知されている場合に作成されたオブジェクトに対しては、確かに機能します。しかし、私はそれを避けます。あなたの場合、所有権は明らかに世界にあるので、世界にそれを削除させます。オブジェクトは自分自身を作成し​​ません。自分自身を削除するべきではないと思います。オブジェクトに対して「Update」関数を呼び出した場合、非常に驚​​くかもしれませんが、World がそれ自体から何も実行せずに、そのオブジェクトが突然存在しなくなります (リストから削除することを除いて、別のフレームで! コードを呼び出すコードオブジェクトの更新はそれに気付かない)。

これに関するいくつかのアイデア

  1. World へのオブジェクト参照のリストを追加します。そのリスト内の各オブジェクトは削除待ちです。これは一般的な手法であり、wxWidgets で閉じられたがメッセージを受け取る可能性があるトップレベル ウィンドウに使用されます。アイドル時間にすべてのメッセージが処理されると、保留リストが処理され、オブジェクトが削除されます。Qtも同様の手法に従っていると思います。
  2. オブジェクトが削除されることを世界に伝えます。世界は適切に情報を得て、やらなければならないことは何でも引き受けます。deleteObject(Object&)多分のような何か。
  3. shouldBeDeleted所有者がオブジェクトを削除したい場合に true を返す関数を各オブジェクトに追加します。

私はオプション 3 を好みます。世界は Update を呼び出すでしょう。その後、オブジェクトを削除する必要があるかどうかを確認し、削除することができます。または、必要に応じて、そのオブジェクトを削除保留中のリストに手動で追加することで、その事実を記憶します。

オブジェクトの関数やデータにアクセスできないのは、いつ、いつなのか分からないときです。たとえば、wxWidgets には、2 つのモードで動作できる wxThread クラスがあります。これらのモードの 1 つ (「デタッチ可能」と呼ばれます) は、そのメイン関数が返された場合 (そしてスレッド リソースを解放する必要がある場合)、スレッドの所有者を待つ代わりに (wxThread オブジェクトによって占有されていたメモリを解放するために) 自分自身を削除します。待機または結合関数を呼び出すオブジェクト。しかし、これは激しい頭痛を引き起こします。どのような状況でも終了する可能性があり、new 以外で作成することはできないため、関数を呼び出すことはできません。かなりの数の人々が、その振る舞いが非常に嫌いだと私に言いました。

参照カウントされたオブジェクトの自己削除は臭いです、私見です。比較してみましょう:

// bitmap owns the data. Bitmap keeps a pointer to BitmapData, which
// is shared by multiple Bitmap instances. 
class Bitmap {
    ~Bitmap() {
        if(bmp->dec_refcount() == 0) {
            // count drops to zero => remove 
            // ref-counted object. 
            delete bmp;
        }
    }
    BitmapData *bmp;
};

class BitmapData {
    int dec_refcount();
    int inc_refcount();

};

それを、参照カウントされた自己削除オブジェクトと比較してください。

class Bitmap {
    ~Bitmap() {
        bmp->dec_refcount();
    }
    BitmapData *bmp;
};

class BitmapData {
    int dec_refcount() {
        int newCount = --count;
        if(newCount == 0) {
            delete this;
        }
        return newCount;
    }
    int inc_refcount();
};

最初のほうがはるかに優れていると思います。適切に設計された参照カウントオブジェクトは「これを削除」しないと思います。これは、結合が増加するためです。参照カウントデータを使用するクラスは、データが自分自身を削除することを知って覚えておく必要があります。参照カウントを減らすことの副作用。~Bitmap のデストラクタで "bmp" がダングリング ポインタになる可能性があることに注意してください。おそらく、ここでは「これを削除」しないほうがずっといいでしょう。

同様の質問への回答「これを削除するのは何ですか」

于 2009-02-06T23:47:28.853 に答える
2

他の人は、「これを削除」の問題について言及しています。つまり、「ワールド」は火の作成を管理するため、その削除も管理する必要があります。

もう1つの問題は、まだ燃えている火で世界が終わる場合です. これが火を消すことができる唯一のメカニズムである場合、孤児またはゴミの火が発生する可能性があります。

あなたが望むセマンティクスのために、「Fire」クラス(または該当する場合はオブジェクト)に「アクティブ」または「アライブ」フラグがあります。その後、世界はときどきオブジェクトのインベントリをチェックし、アクティブでなくなったオブジェクトを削除します。

--

もう 1 つの注意点は、作成されたコードには、パブリック継承よりもはるかに一般的ではありませんが、オブジェクトからプライベートに継承する Fire があることです。これはデフォルトであるためです。おそらく、継承の種類を明示的にする必要があります。私は、あなたが本当に公開継承を望んでいるのではないかと思います。

于 2009-02-07T03:23:12.333 に答える
0

これを削除するのは非常に危険です。「間違っている」ことは何もありません。それは合法です。しかし、それを使用するとうまくいかないことがたくさんあるので、慎重に使用する必要があります.

誰かがオブジェクトへのポインターまたは参照を開いている場合を考えてみてください。

ほとんどの場合、オブジェクトの有効期間は、それを作成した同じオブジェクトによって管理されるべきだと思います。破壊がどこで発生したかを簡単に知ることができます。

于 2009-02-07T00:23:33.990 に答える