[[FrontPage]] *はじめに [#uab6e7b1] ゲームプログラミングの開発環境が変化してきました。~ かつてアセンブラやCで書かれていたゲームプログラムは、C++での開発が主流になったといえるでしょう。~ C#やJavaでのゲームプログラミングも現実的になってきましたが、本格的なゲーム開発はC++が多数派だと思います。~ なぜならば、ゲームは処理速度、メモリ効率、レスポンス、いずれも高い次元で動作することが要求されるからです。 C++11の登場で、C++でのゲームプログラミングも大幅に進化しました。~ それは、アセンブラからCへ、CからC++へと変遷していったときと同じぐらい、大きなインパクトがあります。 C++11により、C++のウイークポイントが解消され、実行速度、メモリ効率、開発効率、ともに大幅な改善がされたためです。 実際にゲーム開発でC++11を本格的に導入して2年ほど経過しましたので、 C++をゲームプログラミングで効率的に使う方法をまとめてみました。 ** もくじ [#o69f245d] - Chap.1 moveの活用 -- 1-1 右辺値参照のおさらい -- 1-2 moveコンストラクタとmove operator = を実装 -- 1-3 std::swapの違い -- 1-4 VisualStudioはmoveコンストラクタを自動生成してくれない -- 1-5 落とし穴 --- 1-5-1 move後のクラスオブジェクトへのアクセス --- 1-5-2 継承クラスのmoveコンストラクタ - Chap.2 ラムダ式の活用 -- 2-1 コールバックの活用 -- 2-1 キャプチャの注意点 -- Chap.3 コピーコンストラクタの廃止 -- 3-1 コピーコンストラクタをmoveコンストラクタに置き換える -- 3-2 shared_ptrをunique_ptrに置き換える - Chap.4 応用編 描画ループの処理 -- 4-1 1/60秒の間隔で呼ばれるメインループ -- 4-2 Threadもco-routineも使わないマルチタスク的処理 -- 4-3 タスクマネージャの紹介 ** CHAPTER-1 moveの活用 [#se0884ad] moveの活用による恩恵は計り知れません。 とくに、速度とメモリ効率、そしてデバッグ効率を重視するゲームプログラミングにおいて、 moveセマンティクスの導入は大きな効果がありました。 なお、moveに関して十分な知識のある方ば、Chap.1は読み飛ばしてください。 *** 1-1 moveのおさらい [#va782358] moveを使う前に、右辺値参照について知っておく必要があります。 このプログラムを見てください。 #sh(cpp){{ string a = "1"; string b = "2"; string c = a + b; // ~~~~~ // 右辺値 }} この"a + b"の部分が右辺値です。~ 右辺値とは、名前のない一時的に生成されるオブジェクトのことです。~ この場合、string型で値が"12"の一時オブジェクトが生成されます。 わかりやすくC++03の書式で置き換えると、 #sh(cpp){{ string a = "1"; string b = "2"; string tmp(a + b); string c = tmp; }} このような動作になります。~ tmpは式の外では不要になる、一時オブジェクトです。 さて、最後の"c = tmp"で行われるコピーが無駄な動作ということは明白ですね。~ C++03では、右辺値を変数に代入する時点で、コピーが発生してメモリと処理速度の無駄が発生していました。 コピーの無駄を省くにはtmpをcにエイリアスしてしまえはば解決しますが、 #sh(cpp){{ string& c = a + b; }} これはエラーになります。"a + b"は右辺値なので、参照型として使うことができません。 C++03では、メモリ上の何処かに生成された一時オブジェクトを、式の外へ持ち出す手段がありませんでした。 C++11では、右辺値参照という新しい機能が追加されました。 #sh(cpp){{ string&& c = a + b; }} これで、C++03で記述する以下の動作とほぼ等しくなります。 #sh(cpp){{ string tmp = a + b; string& c = tmp; }} C++03では、右辺値として生成されたオブジェクトを使う場合、いったんコピーする必要がありました。 では、moveはどこでつかうかというと、 #sh(cpp){{ string&& c = a + b; }} とするかわりに、 #sh(cpp){{ string c = move(a+b); }} とすることで、"a + b"の一時オブジェクトをcに移動することが可能になります。 最初の例と大きな違いがないように見えますが、前者は(右辺値)参照、後者は移動(move)という違いがあります。 一般的には、moveのコストはcopyよりもずっと小さく、stringならばバッファのポインタとサイズをコピーするだけで終わります。 メモリ上にアロケートされた実体はコピーされずにそのまま使われます。 なお、上記の例はわかりやすくするために move(a+b)と書きましたが、a+bは明らかに右辺値なのでmoveは省略できます。 moveを明示的に使うのは、左辺値を右辺値に変換するときに使用します。 #sh(cpp){{ string a = "1"; string c = move(a); // cにaのインスタンスが移動する。 // これ以降はaにアクセスしてはならない。 // aは、ヌケガラ、デガラシ、捨てられたバナナの皮のようなもの。 // アクセスすると、未定義動作の洗礼を受けることになる。 }} この例だと、aをcに移動させているだけで、なんのメリットもないコードです。 しかし、moveは後述するコンストラクタや代入演算子で必要になります。 *** 1-2 moveコンストラクタとmove operator = を実装 [#rb11e3d6] 下記のプログラムは、C++03とC++11では動作が大きく異なります。 #sh(cpp){{ string c = a + b; }} 先ほど解説したとおり、C++11では、"a + b"の一時オブジェクトはcにmoveされます。 なぜmoveされるのか? それは、stringにmove代入演算子とmoveコンストラクタがあるからです。 もし、自前のクラスで、moveコンストラクタやmove代入演算子が定義されていなかったら、moveされません。 #sh(cpp){{ Hoge a = 1; Hoge b = 2; Hoge c = a + b; }} この、3行目の"c = a + b"の動作は、Hogeクラスにmoveコンストラクタが実装されているか否かできまります。 http://melpon.org/wandbox/permlink/7v7e0TFsorASaOzQ