スマートポインタ: メモリ管理の自動化
現代のC++開発において、生のポインタ(Raw Pointer)を使うことは稀です。スマートポインタを使ってメモリリークを撲滅しましょう。
スマートポインタ
ポインタの振る舞いを模倣しつつ、自動メモリ管理(RAII)機能を持つクラスオブジェクト。
std::unique_ptr
排他的所有権を持つスマートポインタ。コピー不可、ムーブのみ可能。最も高速。
std::shared_ptr
共有所有権を持つスマートポインタ。参照カウントで寿命を管理する。
std::weak_ptr
shared_ptrの循環参照を防ぐための、所有権を持たない弱参照ポインタ。
RAII
Resource Acquisition Is Initialization。リソースの取得と初期化、解放と破棄を紐付けるC++の慣習。
詳細解説
なぜ生ポインタではダメなのか?
従来のポインタ(生ポインタ)では、プログラマが手動で `delete` を呼ぶ必要がありました。しかし、これは以下の問題を引き起こします。
- メモリリーク: `delete` を書き忘れる、または例外発生時に `delete` が実行されない。
- 二重解放: 同じメモリを2回 `delete` してしまう。
- ダングリングポインタ: 解放済みのメモリにアクセスしてしまう。
スマートポインタは、変数のスコープ(生存期間)が終わると自動的にデストラクタが呼ばれ、メモリを解放してくれるため、これらの問題を根本から解決します。
3つのポインタ比較
| 種類 | 所有権 | コピー | オーバーヘッド | 主な用途 |
|---|---|---|---|---|
std::unique_ptr | 排他的(1人だけ) | 不可(ムーブのみ) | ほぼゼロ | 基本はこれ。所有権が明確な場合。 |
std::shared_ptr | 共有(複数人) | 可能 | あり(参照カウント) | 複数のオブジェクトでリソースを共有したい場合。 |
std::weak_ptr | なし(弱参照) | 可能 | あり | 循環参照の防止。キャッシュの実装。 |
1. std::unique_ptr
// unique_ptr: 唯一の所有者// std::make_unique を使うのが一般的(C++14以降)std::unique_ptr<int> p1 = std::make_unique<int>(100);
// コピーはできない(コンパイルエラー)// std::unique_ptr<int> p2 = p1;
// 所有権の移動(ムーブ)は可能std::unique_ptr<int> p3 = std::move(p1);
// p1 はもう空っぽ(nullptr)if (!p1) { std::cout << "p1 is empty" << std::endl;}// スコープを抜けると自動的にメモリ解放される2. std::shared_ptr
// shared_ptr: 共有所有者// 参照カウント方式で管理されるstd::shared_ptr<int> sp1 = std::make_shared<int>(200);
{ // コピー可能。参照カウントが増える (+1) std::shared_ptr<int> sp2 = sp1; std::cout << "Count: " << sp1.use_count() << std::endl; // Output: 2}// sp2 がスコープを抜けたのでカウントが減る (-1)
std::cout << "Count: " << sp1.use_count() << std::endl; // Output: 1// 最後の所有者 (sp1) が消えるときにメモリ解放3. std::weak_ptr
// weak_ptr: 所有権を持たない観測者std::shared_ptr<int> sp = std::make_shared<int>(300);std::weak_ptr<int> wp = sp; // 参照カウントは増えない
// 中身にアクセスするには lock() して shared_ptr に昇格させるif (std::shared_ptr<int> locked_sp = wp.lock()) { // 参照先が生きていればここに入れる std::cout << *locked_sp << std::endl;} else { // すでに解放されていた場合 std::cout << "Expired" << std::endl;}実践テクニック
循環参照に注意
`shared_ptr` 同士が互いを参照し合うと、参照カウントが永遠に0にならず、メモリリークが発生します(循環参照)。これを防ぐために、片方を `weak_ptr` にします。親子関係がある場合、親から子へは `shared_ptr`(または `unique_ptr`)、子から親へは `weak_ptr` を使うのが定石です。
演習課題
課題1: 所有権の移動
`std::unique_ptr` を作成し、それを別の関数に渡して、元の変数からはアクセスできなくなることを確認してください。
課題2: 共有カウンタ
`std::shared_ptr` を使って、複数の変数で一つの整数値を共有してください。変数がスコープを抜けるたびに `use_count()` が減ることを確認してください。
合格ライン
基本的には `unique_ptr` を使うべき理由を知っている
`shared_ptr` の循環参照問題と `weak_ptr` での解決法を理解している
`new` や `delete` を直接使わない(`make_unique`を使う)