8
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

C++でメモリリークを防ぐ最も効果的な方法はスマートポインタ

生ポインタでnewdeleteを手動で管理してる人、もうその時代は終わりだよ。

// ❌ 生ポインタ(危険)
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> {};
8
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?