所有権と借用¶
一言で言うと¶
- 所有権 (ownership): 「このオブジェクトを破棄する責任を誰が持つか」。C++設計の背骨で、スマートポインタは所有権を型で表す道具
- 借用 (borrowing): 所有せず「一時的に使わせてもらう」こと(参照・非所有ポインタ)。借用は所有者より長生きしてはいけない——この規律を、C++ではプログラマが(Rustでは型システムが)守る
なぜ「所有」という概念が必要か¶
GCのないC++では、全オブジェクトについて「誰がいつdeleteするか」が決まっていなければ、リークか二重解放になります。これを個別に暗記するのは不可能なので、「所有者は常に1つ(または明示的に共有)」という構造をコードに刻む——それが所有権設計です。
所有の木(良い設計は木構造になる):
Game
├─所有→ Scene
│ ├─所有→ vector<unique_ptr<Entity>>
│ │ └─所有→ unique_ptr<Weapon>
│ └─所有→ ParticleSystem
└─所有→ AssetManager ─所有→ 各アセット
Entityが持つ「Target* (敵への参照)」は借用 ── 木を横断する矢印(所有しない)
型による表現(C++の語彙)¶
| 書き方 | 意味 |
|---|---|
T メンバ / ローカル |
完全所有(寿命が自分と一体)— 第一候補 |
std::unique_ptr<T> |
単独所有(ヒープ上)。ムーブ=所有権の譲渡 |
std::shared_ptr<T> |
共有所有(最後の1人が破棄) |
T& / T* |
借用(非所有)。呼び出しの間だけ/相手が生きてる保証がある間だけ |
std::weak_ptr<T> |
「死んでいるかもしれない相手」への安全な借用 |
関数が unique_ptr<T> を受け取る |
「所有権をよこせ」というシグネチャ |
関数が T&/T* を受け取る |
「借りるだけ」というシグネチャ |
関数シグネチャが所有権の契約書になる——これが規約の力です。Foo(Enemy* e) を見たら「借用(呼び出しの間だけ使う)」と読み、Foo(std::unique_ptr<Enemy> e) なら「あげたら戻ってこない」と読みます。
借用の安全条件¶
借用が安全なのは「所有者の寿命 > 借用の寿命」が構造的に保証されるときだけ:
// 安全な借用: 呼び出しの間だけ(所有者は呼び出し側で生きている)
void Render(const Enemy& e);
// 危険な借用: 保存する借用(所有者がいつ消えるか、この型は知らない)
class Turret {
Enemy* target_ = nullptr; // 敵が先に死んだら? → dangling
};
「保存される借用」がC++設計の急所です。選択肢:
- 所有構造上、相手が確実に長生き(親への参照など)→ 生ポインタ/参照でよい(コメントで根拠を書く)
- 相手が先に死にうる →
weak_ptr(shared管理なら)/ 世代付きハンドル+ID(Object Pool。大量オブジェクトの定石)/ 死亡イベント購読でnullクリア - 毎フレーム検索し直す(保存しない)——単純で意外と正解
Rust の「借用」との関係(用語の出所)¶
「借用」はRustの用語(borrow checker)として有名になりました。Rustはこのページの規律——所有者は1つ、借用は所有者より短命、可変借用は同時に1つ——をコンパイラが強制します。C++は同じ規律を慣習と型(スマートポインタ)で近似する言語です。C++プログラマがRustの概念を学ぶ価値は、「自分が暗黙に守るべき規律の明文化」が手に入ることにあります。
C#との対応¶
- C#は「全員がshared_ptr(GC版)」の世界: 参照を持つ人が全員いなくなるまで死なない。所有権の概念が言語上は不要(だからこそ、いつメモリが返るか誰にも分からない)
- ただしC#にも「論理的な所有権」は存在する: Dispose責任(usingを書くのは誰?)、イベント購読の解除責任、オブジェクトプールの返却責任——C#で曖昧にできていたものをC++は明示させる、と捉えると学びやすい
- Unityの
Destroy(gameObject)は「エンジンがGameObjectを所有し、破棄要求を受け付ける」構造。C#参照が残っていてもエンジン側の実体は消える(だからMissingReferenceが出る)——実はUnityにも所有権問題は存在していた
Unity開発者が誤解しやすい点¶
- 「参照を持っていれば安全」— C++では参照は延命しない(借用)。延命するのはshared_ptrだけ
- 「とりあえずshared_ptrにすれば所有権を考えなくていい」— 所有者不明の設計になり、循環と「いつ消えるか不明」を招く。所有の木を書くのが先
- 「ポインタ引数=危険」— 借用の生ポインタは現代C++の正当な語彙(→ スマートポインタ)
ゲーム開発での使用例¶
- シーングラフ: 親が子をunique_ptrで所有、子は親を生ポインタで借用(親は必ず長生き)
- ターゲット参照: ハンドル(ID+世代)方式が業界定石(大量・頻繁な生死があるため)
- アセット: AssetManagerが所有、ゲームオブジェクトはハンドル/shared_ptrで参照
- 「このシステムを破棄する順序」= 所有と借用の依存関係の逆順(エンジンのShutdown設計)
理解度チェック¶
- 所有権とは何の責任ですか。所有の構造はどんな形が理想ですか。
- 「保存される借用」がなぜ急所で、3つの対処は?
- C#に所有権の概念が(言語上)不要な理由と、それでも残る「論理的所有」の例は?
演習¶
自分のゲーム(またはsamples/component_entity.cpp)について「所有の木」を描き、木を横断する参照(借用)をすべて列挙してください。各借用について「所有者より短命と言える根拠」または「言えない場合の対策」を書き込んでください。
前: 記憶域期間とスコープ・寿命 | カテゴリ目次 | 次: メモリバグ図鑑