第5部 演習 — C++の言語機能¶
問1. 概念確認¶
正誤を判定し、誤りは訂正してください。
Enemy b = a;は、C++ではaとbが同じ実体を指す。std::move(x)はxの中身を移動させる関数である。- Rule of Zeroとは、特殊メンバ関数を一切書かずに済む設計を指す。
- C++のテンプレートは、C#のジェネリックと同様に1つの共有コードで動く。
- 基底クラスのポインタ経由でdeleteするなら、基底に仮想デストラクタが必要。
解答
1. **誤**。コピー(別実体)。共有したければ参照/ポインタを明示([値と参照](01_values_pointers_references.md))。2. **誤**。moveはキャストで何も動かさない。動かすのはムーブコンストラクタ/代入([ムーブ](05_ctor_dtor_copy_move.md))。3. **正**(リソースはvector/unique_ptr等の部品に任せる)。4. **誤**。型ごとにコードが生成される([テンプレート](09_templates_and_concepts.md))。5. **正**([継承と仮想関数](07_inheritance_and_virtual.md))。問2. コード読解¶
std::vector<Enemy> enemies;
Enemy* boss = &enemies.emplace_back(Enemy{}); // 1体目を追加してポインタ保持
enemies.push_back(Enemy{}); // 2体目を追加
boss->hp = 0; // ???
何が起きる可能性があり、なぜですか。安全な書き方を2つ挙げてください。
解答
2体目のpush_backで再確保が起きると、1体目は新しいメモリへ引っ越し、`boss` はdangling(解放済み領域を指す)。`boss->hp = 0` は**未定義動作**([配列と文字列](03_arrays_and_strings.md)、[UB](18_undefined_behavior.md))。安全策: (a) 事前に `reserve` して再確保を防ぐ(脆い)、(b) ポインタではなく**インデックスやハンドル**で参照する(推奨)、(c) `vector問3. 問題のあるコードの改善¶
class TextureManager {
public:
Texture* Load(const std::string& path) {
Texture* t = new Texture(path);
textures_.push_back(t);
return t;
}
~TextureManager() { /* deleteを書き忘れている */ }
private:
std::vector<Texture*> textures_;
};
所有権の観点で問題を指摘し、現代C++に書き直してください。
解答の要点
問題: newの所有者が型から不明(Loadの戻り値は所有?借用?)、デストラクタのdelete忘れでリーク、二重解放の危険。改善: `std::vector問4. 設計比較¶
「ダメージ計算式を差し替え可能にする」を (a) 仮想関数(IDamageFormula)、(b) テンプレート引数(template<class Formula>)、(c) std::function で実装する場合の、決定時期・実行時コスト・柔軟性を比較し、「実行中に式が変わることはないが、ビルド構成で式が違う」場合の選択を答えてください。
解答の要点
(a) 実行時差し替え可・仮想呼び出しコスト・型は1つ。(b) コンパイル時固定・コストゼロ・型が分岐(使う側もテンプレート化)。(c) 実行時差し替え可・型消去のコスト(間接呼び出し+ヒープの可能性)・保存が楽。この条件(実行中不変・構成で違う)なら**(b)テンプレート**(または`constexpr if`/型エイリアス切替)が適合([テンプレートか仮想関数か](../12_decision_guides/06_cpp_choices.md))。問5. デバッグ問題¶
「デバッグビルドでは正常、リリースビルドだけで敵の初期HPがおかしくなる」。疑うべき原因を優先順に2つ挙げ、それぞれの調査手段を書いてください。
解答の要点
(1) **未初期化変数**(デバッグでは0埋めされがち、リリースではゴミ値 — 実装依存の挙動差)。調査: 警告(-Wall/-Wuninitialized)、MSVCの/RTC、UBSan/MSan。(2) **UBを前提にした最適化の顕在化**(範囲外書き込みが配置の違いで露見等)。調査: AddressSanitizer。いずれも「リリースだけ壊れる=最適化のバグ」ではなく**自分のUB**をまず疑うのが定石([UB](18_undefined_behavior.md)、[Debug/Release](../08_build_system/04_build_configurations.md))。問6. 説明問題¶
「C++にGCがないのに、なぜモダンC++ではメモリリークが『あまり問題にならない』と言われるのか」を、RAII・所有権・Rule of Zeroの用語を使って4文以内で説明してください。
解答例
モダンC++では、メモリの解放をプログラマの記憶力ではなく型(デストラクタ)に担わせる——これがRAII。所有権を`unique_ptr`/`vector`などの部品に一元化すれば、所有者の破棄と同時に解放が言語仕様で保証される。ゲームロジック側はRule of Zeroに従い特殊メンバ関数を書かないだけでよい。リークが起きるのは生のnew/deleteや循環`shared_ptr`など、この規約から外れた場所に限られる([RAII](06_raii.md)、[スマートポインタ](16_smart_pointers.md))。問7. 小規模実装問題¶
「クールダウン管理クラス CooldownSet」を実装してください。要件: スキルID(enum class)ごとに残り秒数を管理、Start(id, seconds) / Update(dt) / IsReady(id) const。実装後、以下を自己レビュー:
- const correctnessは守れているか(
IsReadyはconstか) - コンテナ選択の理由(unordered_map? 固定配列?)を言えるか
- Rule of Zeroになっているか
参考実装の骨子
[samples/cooldown_set.cpp](../samples/cooldown_set.cpp)(検証済み)。スキル数が少数固定なら `std::array前: 未定義動作 | カテゴリ目次 | 第6部: C++の内部的な仕組みへ