C++ Learning

第38回 std::unique_ptr ― 単独所有のスマートポインタ

new で取ったヒープを、自分のスコープが終わったら自動で delete してくれる型。コピー不可・ムーブのみなので「所有者は常に 1 人」が保証されます。C のポインタより少し特殊な性質を持ちますが、使い方は直感的。モダン C++ のリソース管理の第一選択です。

このページで押さえること
✅ 最低限ここだけ覚える
  • auto p = std::make_unique<T>(args...); で作る
  • *p / p->member で普通のポインタ感覚
  • コピー不可・ムーブのみ(所有権が 1 つ)
  • スコープ終了で自動 delete(delete を書かない)
⭐ 余裕があれば読む
  • 配列版 std::unique_ptr<T[]>
  • カスタムデリータ(fclose / free など)
  • release() / reset()
  • 関数引数での受け渡し方

1. まず触ってみる ― new/delete を置き換える

C 時代の「malloc して free」、C++ の「new して delete」のパターンを、std::unique_ptr で書き換えるとこうなります。

生ポインタ危険
MyClass* p = new MyClass(42); p->use(); // 例外や早期 return で // delete が呼ばれないリスク delete p;
unique_ptr安全
auto p = std::make_unique<MyClass>(42); p->use(); // スコープ終了で自動 delete // 例外でも確実に解放
ここまでで覚えること(3 つ):
  • std::make_unique<T>(...) で作る
  • 使い方は普通のポインタと同じ*p, p->
  • スコープ終了で自動 delete(書かない)

よくある素朴な疑問

Q. new MyClassmake_unique<MyClass> の違いは?
make_unique はヒープ確保と unique_ptr の生成を一息で行う推奨形。例外安全性重複した型名を書かなくていい利点があります。std::unique_ptr<MyClass> p(new MyClass()) でも動きますが、make_unique を使うのがモダン流儀。

Q. include は?
#include <memory>

Q. コピーできないって不便じゃない?
→ 所有権が1 つという設計。複数から共有したいなら次回の shared_ptr、所有権を移動するだけなら std::move で OK。

2. make_unique と基本操作

作り方C++
#include <memory> // 空(nullptr 相当) std::unique_ptr<int> p1; // 値を持って作る(推奨) auto p2 = std::make_unique<int>(42); // クラスのコンストラクタ引数を渡す auto p3 = std::make_unique<MyClass>(1, 2, "hello");
主要操作API
auto p = std::make_unique<int>(10); *p; // 中身を読む → 10 *p = 99; // 書き換えもできる p->something(); // メンバアクセス(クラスなら) p.get(); // 生ポインタを取得(所有権は維持) if (p) { ... } // 空でないか判定 p.reset(); // 今持ってるものを解放(nullptr に) p.reset(new int(5)); // 古いのを解放し、新しいのを持つ auto* raw = p.release(); // 所有権を放棄(要 delete)

主要メソッドまとめ

release() に注意: これは「手放した」ことを意味するので、呼び出し側が delete する責任を負います。unique_ptr の安全性を捨てる操作なので、基本は使わずに reset() や std::move で済ませましょう。

3. 所有権の移動アニメ

unique_ptrコピー不可ですが、std::move で所有権を移動できます。可視化してみましょう。

▶ 所有権の移動
unique_ptr a
(未作成)
unique_ptr b
(未作成)

コピーしようとするとエラー

コピー禁止エラー
auto a = std::make_unique<int>(42); auto b = a; // コンパイルエラー! auto b = std::move(a); // OK: a → b に所有権移動 // 移動後の a は空(nullptr) // アクセスすると未定義動作(通常はクラッシュ)

4. 関数引数・戻り値での扱い

戻り値 ― unique_ptr を返す

ファクトリ関数典型
std::unique_ptr<Widget> create_widget(int id) { auto w = std::make_unique<Widget>(id); w->init(); return w; // ムーブで返る(コピーされない) } auto p = create_widget(10); // 所有権を受け取る

引数で受ける ― 意図に応じて 3 パターン

所有権を取る値渡し
void store(std::unique_ptr<T> p); store(std::move(my_p)); // 呼び出し側は手放す
借りて使う参照 or 生ポインタ
void use(const T& obj); // 中身にアクセス use(*my_p); void use_ptr(const T* p); // nullptr も受ける use_ptr(my_p.get());
原則:
  • 所有権を渡す意図 → std::unique_ptr<T> の値渡し
  • 借りて使うだけ → const T&(nullptr 無し)や T*(nullptr 有り)
  • std::unique_ptr<T>& はまず使わない(意図が不明瞭)
ここまでで日常は OK
最後は配列とカスタムデリータ。C API と連携するときに効いてきます。

5. 配列とカスタムデリータ

配列版 unique_ptr<T[]>

配列を所有T[]
// make_unique に要素数を渡す → T[] 版になる auto arr = std::make_unique<int[]>(100); arr[0] = 42; // 添字アクセス可 arr[99] = 100; // スコープ終了時 delete[] が自動で呼ばれる

ただし可変長配列は std::vector を使う方が圧倒的に便利です。unique_ptr の配列版を使うのは、サイズ固定で vector を避けたい特殊な場面のみ。

カスタムデリータ

C API のリソース(FILE*SDL_Window* 等)を unique_ptr でラップしたいとき、解放関数を指定できます。

FILE* を RAII にC 連携
#include <cstdio> #include <memory> // デリータはラムダや関数ポインタ auto closer = [](FILE* f) { if (f) fclose(f); }; std::unique_ptr<FILE, decltype(closer)> fp{fopen("data.txt", "r"), closer}; // スコープ終了時に closer(fp.get()) が呼ばれる = fclose
C API を C++ で使うパターン: 生の C の構造体を直接メンバに持たず、unique_ptr + カスタムデリータでラップするのが最も安全。libcurl / SDL / OpenSSL 等の C ライブラリを C++ から使うときの定石です。
広告スペース

確認クイズ

unique_ptr を 4 問で確認。

Q1. unique_ptr を作る推奨形は?

std::unique_ptr<T> p(new T());
T* p = new T(); std::unique_ptr u(p);
auto p = std::make_unique<T>(args...);
std::unique_ptr p = T();
make_unique が現代 C++ の推奨。型名の重複がなく、例外安全性も担保されます。①も動きますが、引数が複雑になると例外安全性の抜け道がある場合があるので make_unique を使う。

Q2. unique_ptr のコピーを試みるとどうなる?

浅いコピーになる
コンパイルエラー
深いコピーになる
実行時エラー
unique_ptr はコピーコンストラクタ・コピー代入が = delete されています。所有者が 2 人になる設計を言語レベルで禁止。移動したい場合は std::move を使います。

Q3. 関数が unique_ptr を返すとき、呼び出し側では何が起きる?

コピーされて性能が落ちる
生ポインタに変換される
ムーブか RVO で所有権が移る(コピーは起きない)
参照カウントが増える
戻り値は右辺値なのでムーブされるか、C++17 ではコピー省略 (RVO) で呼び出し側に直接構築されます。どちらにしろコピーは起きません。

Q4. 次のうちunique_ptr の特徴ではないのは?

所有権は 1 つに限定
スコープ終了で自動 delete
参照カウントを持つ
move で所有権を移動できる
参照カウントはshared_ptr の特徴。unique_ptr はカウンタを持たず、純粋なラッパ(ほぼオーバーヘッドゼロ)。所有権移動には std::move を使う。