3

私は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++プロジェクトのベストプラクティスは何ですか?どのオブジェクトがヒープに割り当てられ、どのオブジェクトがスタックに割り当てられますか?なんで?

  • コンストラクターの複雑さに関する一般的なアドバイスは何ですか?コンストラクターにとってファイルからの読み取りが多すぎませんか?(この問題は主に複雑なコンストラクターから発生するためです。)

4

2 に答える 2

2

しかし、このアプローチには、半分初期化されたオブジェクト、つまりRAIIイディオムに違反するオブジェクトがあるという問題があります。

それはRAIIではありません。概念は、オブジェクトを使用してリソースを管理することです。ヒープメモリ、セマフォ、ファイルハンドルなどのリソースを取得する場合は、所有権をリソース管理クラスに譲渡する必要があります。これが、C++のスマートポインターの目的です。unique_ptrオブジェクトの唯一の所有権を持ちたい場合はを使用するか、shared_ptr複数のポインターに所有権を持たせたい場合はを使用する必要があります。

または、ゲームコンストラクターのヒープに戦場を割り当てます。ただし、コンストラクタの例外に注意する必要があり、デストラクタが戦場を削除するように注意する必要があります。

コンストラクターが例外をスローした場合、オブジェクトのデストラクタは呼び出されず、半分調理されたオブジェクトになってしまう可能性があります。この場合、例外がスローされる前にコンストラクターで行った割り当てを覚えて、それらすべての割り当てを解除する必要があります。ここでも、スマートポインターは、リソースの自動クリーニングに役立ちます。このFAQを参照してください

どのオブジェクトがヒープに割り当てられ、どのオブジェクトがスタックに割り当てられますか?なんで?

可能な限り、オブジェクトをスタックに割り当てるようにしてください。その場合、オブジェクトはそのブロックの範囲内でのみライフを持​​ちます。これが不可能な場合は、ヒープ割り当てを行ってください。たとえば、実行時のサイズしかわからない場合、オブジェクトのサイズが大きすぎてスタックに配置できません。

于 2012-06-23T12:36:16.307 に答える
2

設定から戦場を構築することができます:

explicit battlefield(const settings& s);

または、代わりに、ファクトリ関数を作成してみませんbattlefieldか?

例えば

battlefield CreateBattlefield(const settings& s)
{
    int w = s.read("w");
    int h = s.read("w");
    std::istream& in = s.genistream();
    return battlefield(w, h, gen_random_int(), gen_random_int(), in);
}

game::game(const settings &s) :
    battlefield(CreateBattlefield(s)) {
    // ...
}
于 2012-06-23T12:45:28.953 に答える