2

C++ と SDL を使用して簡単なゲームを作成しようとしています。私の質問は、クラス メンバー変数を格納するためのベスト プラクティスは何かということです。

MyObject obj;
MyObject* obj;

同様の質問でポインターを可能な限り排除することについて多くのことを読みましたが、数年前に読んだいくつかの本で、(すべての重要なオブジェクトに対して) ポインターを頻繁に使用していたことを覚えています。もう 1 つの問題は、SDL がその関数の多くでポインターを返すことです。そのため、SDL オブジェクトを操作するときは "*" を頻繁に使用する必要があります。

また、デフォルトのコンストラクター以外を使用して最初のものを初期化する唯一の方法は初期化子リストを使用することだと思うとき、私は正しいですか?

4

5 に答える 5

2

一般に、ポインター メンバーよりも値メンバーを使用することをお勧めします。ただし、いくつかの例外があります (このリストはおそらく不完全であり、すぐに思いつく理由のみが含まれています)。

  1. メンバーが巨大な場合 (調べるために使用sizeof(MyObject)します)、多くの場合、その違いはアクセスにとって重要ではなく、スタック サイズが問題になる場合があります。
  2. オブジェクトが別のソースから取得される場合 (たとえば、ポインターを作成するファクトリ関数がある場合)、オブジェクトを格納するための代替手段がないことがよくあります。
  3. オブジェクトの動的な型がわからない場合は、通常、ポインターを使用することが唯一の代替手段です。ただし、これは通常ほど一般的ではありません。
  4. オブジェクトが異なるオブジェクト間で共有されている場合など、直接の所有者よりも複雑な関係がある場合は、ポインターを使用するのが最も合理的な方法です。

これらすべての場合において、ポインタを直接使用するのではなく、適切なスマート ポインタを使用します。たとえば、1. には a を使用しstd::unique_ptr<MyObject>、4. にstd::shared_ptr<MyObject>は a を使用することをお勧めします。2. の場合、これらのスマート ポインター テンプレートの 1 つを適切な削除関数と組み合わせて使用​​して、適切なクリーンアップを処理する必要がある場合があります (たとえば、FILE*取得した を削除関数としてfopen()使用するfclose()場合。もちろん、これは作成済みです)。 C++ のように I/O ストリームを使用します)。

一般に、メンバーが正確にどのように表現されているかに関係なく、通常はメンバー初期化子リストでオブジェクト全体を初期化します。ただし、メンバー オブジェクトにコンストラクター引数が必要な場合は、これらをメンバー初期化子リストから渡す必要があります。

于 2013-08-29T23:22:35.550 に答える
1

初期化の場合、オプションが何であるかによって異なりますが、はい、一般的な方法は初期化子リストを使用することです。

「必要な場合を除き、ポインターを使用しないでください」は、一般的に良いアドバイスです。もちろん、そうしなければならない場合もあります。たとえば、オブジェクトが API によって返される場合などです。

また、が小さいnew場合、使用するとかなりの量のメモリと CPU 時間が浪費されます。MyObjectで作成された各オブジェクトnewには、一般的な最新の OS で約 16 ~ 48 バイトのオーバーヘッドがあるため、オブジェクトが単純なタイプの 2 つだけの場合は、実際のストレージよりも多くのオーバーヘッドが発生する可能性があります。大規模なアプリケーションでは、これは簡単に膨大な量になる可能性があります。そしてもちろん、neworの呼び出しには、deleteおそらく数百または数千サイクル (コンストラクターで使用される時間以上) かかります。そのため、実行速度が遅くなり、より多くのメモリを消費するコードになってしまいます。もちろん、混乱してメモリ リークが発生し、メモリ不足が原因でプログラムがクラッシュする可能性がありますが、実際には不足していません。メモリー。

そして、あの有名な「マーフィーの法則」が述べているように、これらのことは、可能な限り最悪で最も厄介な時期に発生する必要があります。本当に良い仕事をしたとき、またはゲームであるレベルで成功したとき、またはなにか。したがって、可能な限りこれらのリスクを回避することは、間違いなく良い考えです.

于 2013-08-29T23:23:50.520 に答える
0

オブジェクトを作成すると、エラーが発生しにくくなるため、ポインターを使用するよりもはるかに優れています。あなたのコードはそれをうまく説明していません。

MyObj* foo;
foo = new MyObj;
foo->CanDoStuff(stuff);
//Later when foo is not needed
delete foo;

他の方法は

MyObj foo;
foo.CanDoStuff(stuff);

メモリ管理は少なくなりますが、実際にはあなた次第です。

于 2013-08-29T23:15:41.493 に答える
0

以前の回答が主張したように、「必要がない限りポインターを使用しないでください」は一般的なプログラミングにとって良いアドバイスですが、最終的にポインターの選択を選択させる可能性のある多くの問題があります。さらに、最初の質問では、参照を使用するオプションを検討していません。したがって、クラス内で 3 種類の変数メンバーに直面する可能性があります。

MyObject obj;
MyObject* obj;
MyObject& obj;

ポインターが NULL かどうかを気にする必要がないため、私は常にポインターではなく参照オプションを検討していました。

また、Dietmar Kühl が指摘したように、ポインターを選択する正当な理由は次のとおりです。

オブジェクトの動的な型がわからない場合は、通常、ポインターを使用することが唯一の代替手段です。ただし、これは通常ほど一般的ではありません。

この点は、大きなプロジェクトに取り組む場合には特に重要だと思います。多くのソース ファイルに配置された多くの独自のクラスがあり、それらをコードの多くの部分で使用すると、コンパイル時間が長くなります。通常のクラス インスタンス (ポインターや参照の代わりに) を使用する場合、クラスのヘッダー ファイルの 1 つを変更するだけで、この変更されたクラスを含むすべてのクラスの再コンパイルが推論されます。この問題の考えられる解決策の 1 つは、ポインターまたは参照を使用するForward 宣言の概念を使用することです (詳細については、こちらを参照してください)。

于 2013-08-30T00:17:56.430 に答える
0

最初に言いたいのは、私はディートマー・キュールとマッツ・ピーターソンの回答に完全に同意するということです。ただし、SDL は純粋な C ライブラリであり、大部分の API 関数が、大量のデータを所有できる構造体の C ポインターを想定していることも考慮する必要があります。そのため、それらをスタックに割り当てるべきではありません (ヒープに割り当てるには new 演算子を使用する必要があります)。さらに、C 言語にはスマート ポインターが含まれていないため、std::unique_ptr::get() を使用して、std::unique_ptr が所有する C ポインターを回復してから、SDL API 関数に送信する必要があります。SDL が C ポインタを使用している間は std::unique_ptr が範囲外にならないようにする必要があるため、これは非常に危険です (std::share_ptr と同様の問題)。そうしないと、SDL が使用している間に std::unique_ptr が C ポインターを削除するため、seg fault が発生します。

C++ プログラム内で純粋な C ライブラリを呼び出す必要があるときはいつでも、RAII の使用をお勧めします。主なアイデアは、C ポインターを所有し、SDL API 関数を呼び出す小さなラッパー クラスを作成することです。次に、クラス デストラクタを使用してすべての C ポインタを削除します。

例:

class  SDLAudioWrap {
  public:
  SDLAudioWrap() { // constructor
     // allocate SDL_AudioSpec
  }
  ~SDLAudioWrap() { // destructor
     // free SDL_AudioSpec
  }

  // here you wrap all SDL API functions that involve  
  // SDL_AudioSpec and that you will use in your program
  // It is quite simple
  void SDL_do_some_stuff() {
    SDL_do_some_stuff(ptr); // original C function 
                            // SDL_do_some_stuff(SDL_AudioSpec* ptr)
  }
  private: 
  SDL_AudioSpec* ptr;
}

これで、プログラムは例外に対して安全になり、SDL が C ポインターを使用しているときにスマート ポインターが C ポインターを削除するという問題が発生する可能性はなくなりました。

更新 1: SDL は C ライブラリであるため、スマート ポインターを使用して C 構造体を適切に管理するには、カスタムの削除クラスが必要になることを忘れています。

具体例: GSL GNU 科学ライブラリ。統合ルーチンには、「gsl_integration_workspace」と呼ばれる構造体の割り当てが必要です。この場合、次のコードを使用して、コードが例外に対して安全であることを確認できます。

 auto deleter= [](gsl_integration_workspace* ptr) {
   gsl_integration_workspace_free(ptr);
 };
 std::unique_ptr<gsl_integration_workspace, decltype(deleter)> ptr4 (
 gsl_integration_workspace_alloc (2000), deleter);

ラッパー クラスを好むもう 1 つの理由

于 2013-08-30T05:46:04.867 に答える