はじめに
C++でメモリリークを防ぐ最も効果的な方法はスマートポインタ。
生ポインタでnewとdeleteを手動で管理してる人、もうその時代は終わりだよ。
// ❌ 生ポインタ(危険)
int* raw = new int(42);
// ... 例外が発生したら? → メモリリーク
delete raw;
// ✓ スマートポインタ(安全)
auto smart = std::make_unique<int>(42);
// スコープを抜けると自動で解放
スマートポインタの種類
| 種類 | 所有権 | 用途 |
|---|---|---|
unique_ptr |
単独所有 | 大半のケース |
shared_ptr |
共有所有 | 複数箇所で共有 |
weak_ptr |
所有しない | 循環参照の防止 |
1. unique_ptr
唯一の所有者を持つスマートポインタ。最も軽量で、デフォルトの選択肢。
#include <memory>
// 作成(make_uniqueを使う)
auto ptr = std::make_unique<Resource>("name");
ptr->use();
// 所有権の移動(コピー不可)
auto ptr2 = std::move(ptr); // ptr は nullptr に
// auto ptr3 = ptr2; // ❌ コピーエラー
// 配列も対応
auto arr = std::make_unique<int[]>(5);
arr[0] = 42;
release と reset
// release: 所有権放棄(生ポインタを返す)
Resource* raw = ptr.release();
delete raw; // 手動で解放が必要!
// reset: 解放して新しいオブジェクトをセット
ptr.reset(new Resource("new"));
ptr.reset(); // 解放のみ
関数の引数・戻り値
// 所有権を渡す
void take_ownership(std::unique_ptr<Resource> ptr);
take_ownership(std::move(my_ptr));
// 所有権を返す
std::unique_ptr<Resource> create_resource() {
return std::make_unique<Resource>("created");
}
// 所有権を渡さない(参照で渡す)
void use_resource(const Resource& res);
use_resource(*my_ptr);
2. shared_ptr
複数の所有者で共有できるスマートポインタ。参照カウントで管理。
auto sp1 = std::make_shared<Resource>("shared");
std::cout << sp1.use_count(); // 1
{
auto sp2 = sp1; // 共有
std::cout << sp1.use_count(); // 2
} // sp2がスコープを抜けてもsp1があるので解放されない
std::cout << sp1.use_count(); // 1
vectorに入れる
std::vector<std::shared_ptr<Resource>> resources;
resources.push_back(sp1);
resources.push_back(sp1);
std::cout << sp1.use_count(); // 3
注意点
-
unique_ptrよりオーバーヘッドがある(参照カウント管理) - 循環参照に注意(後述)
3. weak_ptr(循環参照対策)
shared_ptrを参照するが所有権を持たないポインタ。
循環参照の問題
class Node {
public:
std::shared_ptr<Node> next;
std::shared_ptr<Node> prev; // ❌ 循環参照!
};
auto a = std::make_shared<Node>();
auto b = std::make_shared<Node>();
a->next = b;
b->prev = a; // a → b → a の循環
// どちらもuse_count=2で、永遠に解放されない!
解決策
class Node {
public:
std::shared_ptr<Node> next; // 強参照
std::weak_ptr<Node> parent; // 弱参照
};
auto parent = std::make_shared<Node>();
auto child = std::make_shared<Node>();
parent->next = child;
child->parent = parent; // 弱参照なので循環しない
std::cout << parent.use_count(); // 1(childからの参照はカウントされない)
weak_ptr の使い方
std::weak_ptr<Node> weak = shared;
// 有効性チェック
if (!weak.expired()) {
// shared_ptr に変換して使う
if (auto locked = weak.lock()) {
locked->use();
}
}
4. カスタムデリータ
ファイルハンドルなど、delete以外の解放処理が必要な場合。
// ファイルを自動でcloseする
auto file_deleter = [](FILE* fp) {
if (fp) fclose(fp);
};
std::unique_ptr<FILE, decltype(file_deleter)> file(
fopen("data.txt", "w"),
file_deleter
);
// スコープを抜けると自動でfclose
shared_ptrの場合、デリータは型に含まれない:
std::shared_ptr<FILE> file(fopen("data.txt", "r"), file_deleter);
5. enable_shared_from_this
thisからshared_ptrを取得したい場合。
class Widget : public std::enable_shared_from_this<Widget> {
public:
std::shared_ptr<Widget> get_self() {
return shared_from_this();
}
void register_callback(std::vector<std::shared_ptr<Widget>>& registry) {
registry.push_back(shared_from_this());
}
};
auto widget = std::make_shared<Widget>();
auto self = widget->get_self();
// widget と self は同じオブジェクトを指す
注意: make_sharedで作成されたオブジェクトでのみ使用可能。
6. make_unique / make_shared の利点
例外安全性
// ❌ 危険
void bad(std::unique_ptr<A> a, std::unique_ptr<B> b);
bad(std::unique_ptr<A>(new A()), std::unique_ptr<B>(new B()));
// 評価順序が未規定 → new A() → new B() → unique_ptr構築の順だと
// new B()で例外が出るとnew A()がリーク!
// ✓ 安全
bad(std::make_unique<A>(), std::make_unique<B>());
メモリ効率(shared_ptr)
// 2回のアロケーション
std::shared_ptr<int>(new int(42));
// → オブジェクト + 制御ブロック
// 1回のアロケーション
std::make_shared<int>(42);
// → まとめて確保
7. よくある間違い
❌ 同じ生ポインタから複数のshared_ptrを作る
int* raw = new int(42);
std::shared_ptr<int> sp1(raw);
std::shared_ptr<int> sp2(raw); // ダブルデリート!
❌ deleteしたポインタからshared_ptrを作る
delete raw;
std::shared_ptr<int> sp(raw); // 未定義動作!
❌ thisからshared_ptrを作る
class Bad {
std::shared_ptr<Bad> get_self() {
return std::shared_ptr<Bad>(this); // ダブルデリート!
}
};
解決: enable_shared_from_thisを使う。
使い分けフローチャート
所有権は必要?
├─ No → 生ポインタ or 参照
└─ Yes → 単独所有?
├─ Yes → unique_ptr
└─ No → 循環参照の可能性?
├─ Yes → weak_ptr(一方を弱参照に)
└─ No → shared_ptr
パフォーマンス比較
| 操作 | unique_ptr | shared_ptr |
|---|---|---|
| 構築 | 1 alloc | 1〜2 alloc |
| コピー | 不可 | 参照カウント操作 |
| 解放 | 単純 | 参照カウント確認 |
| サイズ | ポインタ1つ | ポインタ2つ |
結論: 迷ったらunique_ptr。共有が必要ならshared_ptr。
まとめ
// デフォルトはunique_ptr
auto ptr = std::make_unique<MyClass>();
// 共有が必要ならshared_ptr
auto shared = std::make_shared<MyClass>();
// 循環参照対策にweak_ptr
std::weak_ptr<MyClass> weak = shared;
// thisからshared_ptrが欲しければenable_shared_from_this
class MyClass : public std::enable_shared_from_this<MyClass> {};