エンジンコンポーネントのコンストラクタとデストラクタにロジックを配置する代わりに、個別の初期化メソッドとクリーンアップメソッドを使用する必要があるのはなぜですか?


9

私は自分のゲームエンジンに取り組んでおり、現在マネージャーを設計しています。メモリ管理については、コンストラクタとデストラクタを使用するよりもInit()CleanUp()関数を使用する方が良いことを読みました。

これらの関数がどのように機能し、どのようにエンジンに実装できるかを確認するために、C ++コードの例を探していました。どのように機能しInit()、どのようにしCleanUp()てエンジンに実装できますか?



C ++については、stackoverflow.com / questions / 3786853 /…を参照してください。Init()を使用する主な理由は1)ヘルパー関数を使用してコンストラクターで例外とクラッシュを防止する2)派生クラスの仮想メソッドを使用できる3)循環依存関係を回避する4)コードの重複を回避するプライベートメソッドとして
brita_

回答:


12

実際には非常に単純です。

あなたの設定を行うコンストラクターを持つ代わりに、

// c-family pseudo-code
public class Thing {
    public Thing (a, b, c, d) { this.x = a; this.y = b; /* ... */ }
}

...すべてではほとんどあるいは何行うあなたのコンストラクタを持っている、と呼ばれる方法の書き込み.init.initialize、あなたのコンストラクタは、通常どうなるのかだろう。

public class Thing {
    public Thing () {}
    public void initialize (a, b, c, d) {
        this.x = a; /*...*/
    }
}

だから今のように行く代わりに:

Thing thing = new Thing(1, 2, 3, 4);

行ってもいい:

Thing thing = new Thing();

thing.doSomething();
thing.bind_events(evt_1, evt_2);
thing.initialize(1, 2, 3, 4);

ここでの利点は、システムで依存性注入/制御の反転をより簡単に使用できることです。

言う代わりに

public class Soldier {
    private Weapon weapon;

    public Soldier (name, x, y) {
        this.weapon = new Weapon();
    }
}

あなたは兵士を構築し、彼に武器を渡す装備メソッドを彼に与えることができ、それからコンストラクタ関数の残りのすべてを呼び出します。

したがって、今度は、ある兵士がピストルを持ち、別の兵士がライフルを持ち、別の兵士がショットガンを持っている敵をサブクラス化する代わりに、それが唯一の違いです。

Soldier soldier1 = new Soldier(),
        soldier2 = new Soldier(),
        soldier3 = new Soldier();

soldier1.equip(new Pistol());
soldier2.equip(new Rifle());
soldier3.equip(new Shotgun());

soldier1.initialize("Bob",  32,  48);
soldier2.initialize("Doug", 57, 200);
soldier3.initialize("Mike", 92,  30);

破壊についても同様です。特別なニーズがある場合(イベントリスナーの削除、配列からのインスタンスの削除/操作している構造体など)は、手動で呼び出して、発生したプログラムのいつどこで発生しているかを正確に把握します。

編集


Kryotanが指摘したように、これは元の投稿の"How"に答えるものですが、 "Why"をうまく実行していません。

上記の答えでおそらくわかるように、以下の間に大きな違いはないかもしれません。

var myObj = new Object();
myObj.setPrecondition(1);
myObj.setOtherPrecondition(2);
myObj.init();

そして書く

var myObj = new Object(1,2);

より大きなコンストラクター関数があるだけです。
15または20の事前条件を持つオブジェクトに対して行われるべき議論があります。これにより、コンストラクターが非常に扱いにくくなり、インターフェースにそれらを引き出すことにより、物事をより見やすく覚えやすくなります。 、インスタンス化がどのように機能するかを1レベル上のレベルで確認できます。

オブジェクトのオプション構成は、これに対する自然な拡張です。オプションで、オブジェクトを実行する前に、インターフェースで値を設定します。
JSにはこのアイデアの優れたショートカットがいくつかあります。これは、より強く型付けされたcのような言語では場違いに見えるだけです。

とは言っても、コンストラクターで長い引数リストを扱っている場合、オブジェクトが大きすぎて、あまりにも多くのことを行う可能性があります。繰り返しますが、これは個人的な好みの問題であり、例外は広範囲に存在しますが、オブジェクトに20のオブジェクトを渡す場合は、小さいオブジェクトを作成することにより、そのオブジェクトの処理を減らす方法を見つける可能性があります。

より適切な理由、そして広く適用できる理由の1つは、オブジェクトの初期化が、現在持っていない非同期データに依存していることです。

オブジェクトが必要であることはわかっているので、とにかくオブジェクトを作成しますが、正しく機能させるには、サーバーまたは現在ロードする必要がある別のファイルからのデータが必要です。

繰り返しになりますが、必要なデータを巨大なinitに渡すか、インターフェースを構築するかは、オブジェクトのインターフェースやシステムの設計にとって重要であるのと同じくらい、概念にとってそれほど重要ではありません...

しかし、オブジェクトの構築に関しては、次のようなことをするかもしれません:

var obj_w_async_dependencies = new Object();
async_loader.load(obj_w_async_dependencies.async_data, obj_w_async_dependencies);

async_loader ファイル名やリソース名などを渡されて、そのリソースをロードする可能性があります-サウンドファイルや画像データをロードしたり、保存されたキャラクターの統計をロードしたりするかもしれません...

...そしてそのデータをにフィードバックしobj_w_async_dependencies.init(result);ます。

この種のダイナミックはウェブアプリで頻繁に見られます。
より高いレベルのアプリケーションの場合、必ずしもオブジェクトの構築に含まれているとは限りません。たとえば、ギャラリーはすぐに読み込まれて初期化され、次にストリーミング中に写真を表示する可能性があります。これは、非同期初期化ではありませんが、より頻繁に見られる場所です。 JavaScriptライブラリ。

1つのモジュールが別のモジュールに依存している可能性があるため、そのモジュールの初期化は、依存関係のロードが完了するまで延期される可能性があります。

このゲーム固有のインスタンスについては、実際のGameクラスを検討してください。

なぜ、.startまたは.runコンストラクタで呼び出せないのですか?
リソースをロードする必要があります-残りのすべてはほぼ定義されており、問題ありませんが、データベース接続なしで、またはテクスチャー、モデル、サウンド、またはレベルなしでゲームを実行しようとすると、それはうまくいきません。特に興味深いゲーム...

... Gameつまり、「先に進む」メソッドに、より興味深い名前を付ける.init(または逆に、初期化をさらに分解して、ロードを分離する)ことを除いて、典型的なの違いは何ですか?ロードされたものをセットアップし、すべてがセットアップされたときにプログラムを実行します)。


2
手動で呼び出して、プログラムのどこで、どこで起こっているかを正確に知ることができます。」C ++でデストラクタが暗黙的に呼び出されるのは、スタックオブジェクト(またはグローバル)の場合のみです。ヒープに割り当てられたオブジェクトは、明示的に破棄する必要があります。したがって、オブジェクトの割り当てがいつ解除されるかは常に明確です。
Nicol Bolas 2013年

6
異なる種類の武器の注入を可能にするためにこの個別のメソッドが必要である、またはこれがサブクラスの急増を回避する唯一の方法であると言うのは正確ではありません。コンストラクタを介して武器インスタンスを渡すことができます!したがって、これは説得力のあるユースケースではないため、私からは-1です。
カイロタン2013年

1
-1私からも、カイロタンとほぼ同じ理由で。あなたは非常に説得力のある議論をしないでください、これはすべてコンストラクタで行うことができたでしょう。
ポールマンタ2013年

はい、コンストラクタとデストラクタで実現できます。彼は、テクニックの使用例と、その仕組みや理由ではなく、理由と方法を尋ねました。コンポーネントベースのシステムでセッター/バインディングメソッドを使用しているのに対し、コンストラクターで渡されたDIのパラメーターを実際に使用するのは、インターフェースの構築方法にかかっています。しかし、オブジェクトに20のIOCコンポーネントが必要な場合、それらすべてをコンストラクターに入れますか?あなたはできる?もちろんできます。しますか?多分そうでないかもしれません。あなたが選択した場合ではないに、あなたは必要なのです.initが、おそらく、多分、ありません。エルゴ、有効なケース。
Norguard 2013年

1
@Kylotan私は実際に質問のタイトルを編集して理由を尋ねました。OPは「方法」だけを尋ねました。「なぜ」はプログラミングについて何か知っている人にとっては些細なことなので、「なぜ」を含めるように質問を拡張しました(「あなたが持っているはずのロジックを別の関数に移動して呼び出します」)と「なぜ」より興味深い/一般的です。
Tetrad 2013年

17

InitとCleanUpの方が優れているとおっしゃっていても、その理由もお分かりでしょう。彼らの主張を正当化しない記事は読む価値はありません。

初期化関数とシャットダウン関数を別々にすると、呼び出す順序を選択できるため、システムのセットアップと破棄が容易になります。一方、コンストラクターはオブジェクトの作成時に正確に呼び出され、デストラクタはオブジェクトが破棄されるときに呼び出されます。2つのオブジェクト間に複雑な依存関係がある場合、それらが設定される前に両方のオブジェクトが存在する必要があることがよくあります。

一部の言語には、参照カウントとガベージコレクションによってオブジェクトがいつ破棄されるかを知ることが困難になるため、信頼できるデストラクタがありません。これらの言語では、ほとんど常にシャットダウン/クリーンアップメソッドが必要であり、対称性のためにinitメソッドを追加したいものもあります。


ありがとうございます。記事には例がなかったので、主に例を探しています。私の質問がそれについて不明確だった場合はお詫び申し上げますが、現在編集しています。
Friso 2013年

3

一番の理由は、プールを許可することです。
InitとCleanUpがある場合は、オブジェクトが削除されたときにCleanUpを呼び出して、オブジェクトを同じタイプのオブジェクトのスタック(「プール」)にプッシュできます。
次に、新しいオブジェクトが必要なときはいつでも、プールから1つのオブジェクトをポップすることができます。または、プールが空の場合は、新しいオブジェクトを作成する必要があります。次に、このオブジェクトでInitを呼び出します。
良い戦略は、ゲームが「良い」数のオブジェクトで始まる前にプールを事前に満たすことです。そのため、ゲーム中にプールされたオブジェクトを作成する必要はありません。
一方、「new」を使用し、不要になったオブジェクトの参照を停止すると、いつか再収集する必要のあるガベージが作成されます。この回収は、JavaScriptなどのシングルスレッド言語では特に悪いことです。ガベージコレクターは、不要になったオブジェクトのメモリを回収する必要があると評価すると、すべてのコードを停止します。ゲームが数ミリ秒間ハングし、プレイ体験が台無しになります。
-あなたはすでに理解しています-:すべてのオブジェクトをプールした場合、再収集は発生しないため、ランダムなスローダウンは発生しません。

また、メモリを割り当てて新しいオブジェクトを初期化するよりも、プールからのオブジェクトで初期化を呼び出す方がはるかに高速です。
ただし、オブジェクトの作成はパフォーマンスのボトルネックにならないことが多いため、速度の向上はそれほど重要ではありません...必死のゲーム、パーティクルエンジン、または計算に集中的に2D / 3Dベクトルを使用する物理エンジンなどのいくつかの例外があります。ここでは、プールを使用することにより、速度とガベージの作成の両方が大幅に改善されています。

Rq:Init()がすべてをリセットする場合は、プールされたオブジェクトのCleanUpメソッドが不要な場合があります。

編集:この投稿に返信することで、Javascriptでのプールについて私が作成した小さな記事をまとめる気になった。
興味がある場合は、ここで見つけることができます。http
//gamealchemist.wordpress.com/


1
-1:オブジェクトのプールを作るためだけにこれを行う必要はありません。これは、明示的なデストラクタコールによって、割り当てを構築から配置、分離を解除して削除から削除するだけで実行できます。したがって、これは、コンストラクター/デストラクターを初期化メソッドから分離する正当な理由ではありません。
Nicol Bolas 2013

新しい配置はC ++固有であり、少し難解です。
カイロタン2013

+1 c +では他の方法でこれを行うことが可能かもしれません。しかし、他の言語ではありません...そしておそらくこれが、ゲームオブジェクトでInitメソッドを使用する理由の1つにすぎません。
喜界丸

1
@Nicol Bolas:私はあなたが過剰反応していると思います。プーリングを実行する方法が他にもあるという事実(C ++に固有の複雑な方法について言及している)は、個別のInitを使用することが多くの言語でプーリングを実装するための優れたシンプルな方法であるという事実を無効にしません。私の好みは、GameDevでは、より一般的な答えです。
GameAlchemist 2013

@VincentPiel:C ++で新規およびそのような「複雑な」配置をどのように使用していますか?また、GC言語で作業している場合は、オブジェクトにGCベースのオブジェクトが含まれる可能性が高くなります。それで、彼らも彼らそれぞれを投票しなければなりませんか?したがって、新しいオブジェクトを作成するには、プールから新しいオブジェクトの束を取得する必要があります。
Nicol Bolas 2013

0

あなたの質問は逆転しました...歴史的に言えば、より適切な質問は:

なぜ 構築と初期化が混同されているのか、つまり、これらのステップを個別に行わないのはなぜですか?確かにこれはSoCに反しますか?

C ++の場合、RAIIの目的は、リソースの獲得と解放がオブジェクトの存続期間に直接結び付けられることです。これにより、リソースの解放が保証されます。そうですか?一部。スタックベースの/自動変数のコンテキストで100%満たされます。関連するスコープを離れると、自動的にデストラクタが呼び出され、これらの変数が解放されます(つまり、修飾子automatic)。ただし、ヒープ変数のdelete場合、デストラクタを実行するために明示的に呼び出す必要があるため、この非常に便利なパターンは悲しいことに壊れてしまいます。そうしないと、RAIIが解決しようとする試みに噛み付いてしまいます。ヒープに割り当てられた変数のコンテキストでは、C ++はC(deletevsfree())構築を初期化と融合させる一方で、次の点で悪影響があります。

Cでのゲーム/シミュレーション用のオブジェクトシステムの構築は、C ++およびそれ以降の古典的なOO言語の前提をより深く理解することで、RAIIやその他のOO中心のパターンの制限に多大な光を当てることを強くお勧めします(C ++はCで構築されたOOシステムとして始まったことを思い出してください)。

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