私はC++をかなりよく知っていると思います。そして、「おもちゃ」プログラムよりも少し大きいものを実装することを考えています。スタックメモリとヒープメモリとRAIIイディオムの違いを知っています。
私が単純なクラスポイントを持っていると仮定しましょう:
class point {
public:
int x;
int y;
point(int x, int y) : x(x), y(y) {}
};
オブジェクトが小さいので、常にスタックにポイントを割り当てます。64ビットマシンsizeof(point) == sizeof(void*)
では、が間違っていなければ、さらに進んで、デフォルトで値でポイントを渡します。
ここで、クラスゲームで使用したい、より複雑なクラスの戦場を想定しましょう。
class battlefield {
public:
battlefield(int w, int h, int start_x, int start_y, istream &in) {
// Complex generation of a battlefield from a file/network stream/whatever.
}
};
私はRAIIと、オブジェクトがスコープを離れるときの自動クリーンアップが本当に好きなので、スタックに戦場を割り当てたくなります。
game::game(const settings &s) :
battlefield(s.read("w"), s.read("h"), gen_random_int(), gen_random_int(), gen_istream(s.read("level_number"))) {
// ...
}
しかし、私は今いくつかの問題を抱えています:
このクラスにはゼロ引数コンストラクターがないため、戦場で使用するクラスの初期化リストで初期化する必要があります。どこかからのistreamが必要なため、これは面倒です。これは次の問題につながります。
複雑なコンストラクターは、ある時点で「雪だるま式」になります。ゲームクラスで戦場を使用し、初期化リストでゲームのコンストラクターを初期化すると、ゲームのコンストラクターもかなり複雑になり、ゲーム自体の初期化も面倒になる可能性があります。(ゲームコンストラクターの引数としてistreamを使用することにした場合)
複雑なパラメータを入力するための補助関数が必要です。
この問題には2つの解決策があります。
オブジェクトを初期化しない戦場用の単純なコンストラクターを作成します。しかし、このアプローチには、半分初期化されたオブジェクト、つまりRAIIイディオムに違反するオブジェクトがあるという問題があります。そのようなオブジェクトでメソッドを呼び出すと、奇妙なことが起こる可能性があります。
game::game(const settings &s) { random_gen r; int x = r.random_int(); int y = r.random_int(); ifstream in(s.read("level_number")); in.open(); this->battlefield.init(s.read("w"), s.read("h"), x, y, in); // ... }
または、ゲームコンストラクターのヒープに戦場を割り当てます。ただし、コンストラクタの例外に注意する必要があり、デストラクタが戦場を削除するように注意する必要があります。
game::game(const settings &s) { random_gen r; int x = r.random_int(); int y = r.random_int(); ifstream in(s.read("level_number")); in.open(); this->battlefield = new battlefield(s.read("w"), s.read("h"), x, y, in); // ... }
私が考えている問題をご覧いただければ幸いです。私に生じるいくつかの質問は次のとおりです。
私が知らないこの状況のデザインパターンはありますか?
大規模なC++プロジェクトのベストプラクティスは何ですか?どのオブジェクトがヒープに割り当てられ、どのオブジェクトがスタックに割り当てられますか?なんで?
コンストラクターの複雑さに関する一般的なアドバイスは何ですか?コンストラクターにとってファイルからの読み取りが多すぎませんか?(この問題は主に複雑なコンストラクターから発生するためです。)