C言語 › C++ › 第38回 std::unique_ptr
第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 を置き換える
2. make_unique と基本操作
3. 所有権の移動アニメ
4. 関数引数・戻り値での扱い
5. 配列とカスタムデリータ
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 MyClass と make_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)
主要メソッドまとめ
get():中の生ポインタを返す(所有権は unique_ptr のまま )
reset():現在の中身を delete し、空にする
reset(新 p):古いのを delete、新しい生ポインタを所有
release():生ポインタを返し、自身は空に(呼び出し元が delete 責任)
operator bool:空でないか
release() に注意: これは「手放した」ことを意味するので、呼び出し側が delete する責任 を負います。unique_ptr の安全性を捨てる操作なので、基本は使わずに reset() や std::move で済ませましょう。
3. 所有権の移動アニメ
unique_ptr はコピー不可 ですが、std::move で所有権を移動できます。可視化してみましょう。
▶ 所有権の移動
auto a = make_unique<int>(42);
auto b = std::move(a);
スコープ終了
↺ リセット
unique_ptr a (未作成)
unique_ptr b (未作成)
ヒープ上の int(42)
コピーしようとするとエラー
コピー禁止 エラー
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 を使う。