ゲームオブジェクトがC ++で誤って削除されるのを防ぐ方法


20

私のゲームには、プレイヤーで神風が爆発するモンスターがいるとしましょう。このモンスターの名前をランダムに選んでみましょう:クリーパー。そのため、Creeperクラスには次のようなメソッドがあります。

void Creeper::kamikaze() {
    EventSystem::postEvent(ENTITY_DEATH, this);

    Explosion* e = new Explosion;
    e->setLocation(this->location());
    this->world->addEntity(e);
}

イベントはキューに入れられず、すぐにディスパッチされます。これにより、Creeperの呼び出し内のどこかでオブジェクトが削除されますpostEvent。このようなもの:

void World::handleEvent(int type, void* context) {
    if(type == ENTITY_DEATH){
        Entity* ent = dynamic_cast<Entity*>(context);
        removeEntity(ent);
        delete ent;
    }
}

Creeperオブジェクトはkamikazeメソッドの実行中に削除されるため、にアクセスしようとするとクラッシュしますthis->location()

1つの解決策は、イベントをバッファにキューイングし、後でそれらをディスパッチすることです。それはC ++ゲームの一般的なソリューションですか?それはちょっとしたハックのように感じますが、それは異なるメモリ管理プラクティスを持つ他の言語での私の経験のためかもしれません。

C ++では、オブジェクトがそのメソッドの1つから誤って自分自身を削除するこの問題に対するより一般的な解決策はありますか?


6
ええと、開始の代わりにkamikazeメソッドのENDでpostEventを呼び出すのはどうですか?
ハックワース

この特定の例で機能する@Hackworthですが、より一般的なソリューションを探しています。私はどこからでもイベントを投稿できるようにしたいのですが、クラッシュを引き起こすことを恐れないでください。
トムダリング

また、の実装を見ることができます autorelease、Objective-Cのは、「ほんの少し」まで削除が保留されます。
クリスバートブラウン

回答:


40

削除しないでください this

暗黙的にでも。

-今まで-

メンバー関数の1つがスタック上にある間にオブジェクトを削除すると、問題が発生します。その結果(偶然)発生するコードアーキテクチャは、客観的に悪いものであり、危険であり、すぐリファクタリングする必要があります。この場合、モンスターが「World :: handleEvent」の呼び出しを許可される場合、どのような状況でも、その関数内のモンスターを削除しないでください!

(この特定の状況では、私の通常のアプローチは、モンスターに「デッド」フラグを設定し、ワールドオブジェクトまたはそれに類似したものを持たせて、フレームごとにその「デッド」フラグをテストし、それらのオブジェクトを削除することですワールド内のオブジェクトのリストから、それを削除するか、モンスタープールまたは適切なものに戻すかのいずれかです。この時点で、ワールドは削除に関する通知も送信するため、世界の他のオブジェクトは、モンスターが既存のオブジェクトを停止し、保持している可能性のあるポインタをドロップできます。現在、オブジェクトが処理されていないことがわかっている安全な時点でこれを行うため、スタックがポイントまで巻き戻される心配はありません「this」ポインタは解放されたメモリを指します。)


6
括弧内の答えの後半は役に立ちました。フラグのポーリングは良い解決策です。しかし、私はそれを避ける方法を尋ねていました。それが良いことでも悪いことでもないかどうかではありませんでした。「Xを誤って実行しないようにするにはどうすればよいですか?」という質問があり、あなたの答えが「Xを誤って、絶対にしない」と太字で示している場合、実際には質問に答えません。
トムダリング

7
私は私の答えの前半で私がしたコメントの後ろに立っています、そして、私は彼らが最初に述べられたように質問に完全に答えると感じます。ここで繰り返します重要な点は、オブジェクトがそれ自体を削除しないことです。 今まで。削除するために他の人を呼び出すことはありません。 今まで。代わりに、オブジェクトを所有し、オブジェクトを破棄する必要があるときに気付く責任がある、オブジェクトの外部に何か他のものが必要です。これは「モンスターが死んだとき」のことではありません。これは、すべてのC ++コードで常に、どこでも、いつでも使用できます。 例外なし。
トレバーパウエル

3
@TrevorPowellあなたが間違っていると言っているのではありません。実際、私はあなたに同意します。私はそれが実際に尋ねられた質問に答えないと言っているだけです。あなたが私に尋ねた場合「ようなものだどのように私は私のゲームの中にオーディオのですか?」と私の答えだった「私はあなたがオーディオを持っていないと信じてすることはできません。今、あなたのゲームでオーディオを入れてください。」そして、ボトムIダウン括弧内にput ((FMODを使用できます))、これは実際の答えです。
トムダリング

6
@TrevorPowellこれはあなたが間違っているところです。選択肢がわからなければ、それは「単なる規律」ではありません。私が与えたコード例は純粋に理論的なものです。私はすでにそれが悪い設計であることを知っていますが、私のC ++は錆びているので、実際に自分が望むものをコーディングする前に、ここでより良い設計について尋ねると思いました。だから私は代替設計について尋ねに来ました。「削除フラグを追加」は代替です。「絶対にやらない」という選択肢はありません。すでに知っていることを伝えているだけです。質問を適切に読まずに答えを書いたように感じます。
トムダリング

4
@Bobby質問は「Xを実行しない方法」です。単に「Xを実行しないでください」と言うのは無価値な答えです。質問が「Xをやった」、「Xをやろうと考えていた」、またはその変種であった場合、メタディスカッションのパラメーターは満たされますが、現在の形ではありません。
ジョシュアドレーク

21

バッファー内のイベントをキューに入れる代わりに、バッファー内の削除をキューに入れます。遅延削除には、ロジックを大幅に簡素化する可能性があります。オブジェクトに何も面白いことが起きていないことがわかっているときに、フレームの最後または最初にメモリを実際に解放し、どこからでも削除できます。


1
おもしろいことですがNSAutoreleasePool、Objective-Cのこのような状況がどれほど素晴らしいものかを考えていたからです。作るために必要がある場合がありますDeletionPoolC ++のテンプレートか何かで。
トムダリン

@TomDallingオブジェクトの外部にバッファを作成する場合、1つのフレームで複数の理由でオブジェクトを削除したい場合があり、数回削除しようとする可能性があることに注意する必要があります。
ジョンCalsbeek

本当です。ポインターをstd :: setに保持する必要があります。
トムダリング

5
削除するオブジェクトのバッファではなく、オブジェクトにフラグを設定することもできます。実行中にnewまたはdeleteの呼び出しを回避し、オブジェクトプールに移動することをどれだけ実現したいかがわかれば、それはより簡単かつ高速になります。
ショーンミドルディッチ

4

世界に削除を処理させる代わりに、別のクラスのインスタンスを、削除されたすべてのエンティティを保存するバケットとして機能させることができます。この特定のインスタンスはリッスンする必要がありますENTITY_DEATHイベントを、それらをキューにように処理する必要があります。Worldその後、今度はエンティティインスタンスの実際の削除を実行することになる、これらのインスタンスを反復処理し、フレームは、このバケットをレンダリングし、「クリア」された後、ポスト死の操作を行うことができます。

このようなクラスの例は次のようになります。http//ideone.com/7Upza


+1、エンティティに直接フラグを立てる代わりに使用できます。より直接的には、Worldクラスのエンティティのアライブリストとデッドリストを直接保持するだけです。
ローランクーヴィドゥー

2

ゲーム内のすべてのゲームオブジェクトの割り当てに使用されるファクトリを実装することをお勧めします。そのため、自分でnewを呼び出す代わりに、ファクトリーに何かを作成するように指示します。

例えば

Object* o = gFactory->Create("Explosion");

オブジェクトを削除するたびに、ファクトリは次のフレームでクリアされるバッファーにオブジェクトをプッシュします。遅延破壊は、ほとんどのシナリオで非常に重要です。

また、すべてのメッセージを1フレームの遅延で送信することを検討してください。すぐに送信する必要がある例外は2、3しかありませんが、ほとんどの場合、


2

管理メモリをC ++で自分で実装できるので、 ENTITY_DEATH呼び出され発生するのは、参照の数が1つ減ることだけです。

後で@Johnがすべてのフレームの物beいで提案したように、どのエンティティが役に立たないか(参照がゼロのエンティティ)を確認して削除できます。たとえば、使用できますboost::shared_ptr<T>ここに記載されています)またはC ++ 11(VC2010)を使用している場合std::tr1::shared_ptr<T>


ただstd::shared_ptr<T>、技術レポートではありません!—カスタム削除機能を指定する必要があります。指定しない場合、参照カウントがゼロに達するとすぐにオブジェクトも削除されます。
左辺約

1
@leftaroundaboutそれは本当に依存しています、少なくとも私はgccでtr1を使用する必要がありました。しかし、VCではその必要はありませんでした。
Ali1S232

2

プールを使用し、実際にオブジェクトを削除しないでください。代わりに、登録先のデータ構造を変更してください。たとえば、オブジェクトのレンダリングには、シーンオブジェクトと、レンダリング、衝突検出などのために何らかの方法で登録されたすべてのエンティティがあります。オブジェクトを削除する代わりに、シーンから切り離し、デッドオブジェクトプールに挿入します。この方法は、メモリの問題(オブジェクト自体の削除など)を防止するだけでなく、プールを正しく使用するとゲームの速度を上げる可能性があります。


1

ゲームで行ったことは、新しい配置を使用することでした

SomeEvent* obj = new(eventPool.alloc()) new SomeEvent();

eventPoolは切り分けられたメモリの単なる大きな配列であり、各セグメントへのポインタが格納されていました。したがって、alloc()はメモリの空きブロックのアドレスを返します。eventPoolでは、メモリはスタックとして扱われたため、すべてのイベントが送信された後、スタックポインターをリセットして配列の先頭に戻します。

イベントシステムの動作方法により、Evetnsでデストラクタを呼び出す必要はありませんでした。そのため、プールは単にメモリブロックを空きメモリとして登録し、割り当てます。

これにより、大幅にスピードアップしました。

また...開発中に動的に割り当てられたすべてのオブジェクトに実際にメモリプールを使用しました。ゲームが終了したときにプールにオブジェクトが残っている場合(通常)、メモリリークを見つけるのに最適な方法だったためメモリリーク。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.