C++: スマートポインタ応用

最終更新:2026-08-31

レッスン45では、ムーブセマンティクスについて学びました。

ここでは、スマートポインタ——C++メモリ管理の救世主について学びます。

スマートポインタを使用すれば、new/delete を忘れてもメモリリークしません。


1. スマートポインタの基本

(1) 1.1 スマートポインタとは?

スマートポインタ動的メモリを管理するテンプレートクラスで、RAIIでメモリを自動解放します。

スマートポインタの種類:

スマートポインタ 機能 使用場面
unique_ptr 専有所有権 1つのオーナーのみ
shared_ptr 共有所有権 複数のオーナー
weak_ptr 非所有参照 循環参照の解決



2. unique_ptr

(1) 2.1 基本的な使い方

unique_ptr専有所有権のスマートポインタで、コピー不可、ムーブ可能です。

▶ サンプル 2:コード例(難易度 ⭐)

CPP
#include <iostream>
#include <memory>

int main() {
    std::unique_ptr<int> p1(new int(42));
    std::cout << *p1 << std::endl; // 出力:42
    
    // std::unique_ptr<int> p2 = p1; // ❌ エラー!コピー不可
    std::unique_ptr<int> p2 = std::move(p1); // ✅ ムーブ可能
    std::cout << *p2 << std::endl; // 出力:42
    
    return 0;
} // p2が自動解放
▶ 試してみよう

(2) 2.2 カスタムデリータ

▶ サンプル:unique_ptrでファイル管理(難易度 ⭐⭐)

CPP
#include <iostream>
#include <memory>
#include <cstdio>

// カスタムデリータ
struct FileDeleter {
    void operator()(FILE* fp) const {
        if (fp) {
            fclose(fp);
            std::cout << "ファイルを閉じました" << std::endl;
        }
    }
};

int main() {
    std::unique_ptr<FILE, FileDeleter> file(fopen("test.txt", "w"));
    // fcloseを呼ぶ必要なし、unique_ptrがFileDeleterで自動処理
    
    return 0;
}
▶ 試してみよう


3. shared_ptr

(1) 3.1 基本的な使い方

shared_ptr共有所有権のスマートポインタで、参照カウントでメモリを管理します。

▶ サンプル:shared_ptrの基本(難易度 ⭐)

CPP
#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> p1 = std::make_shared<int>(42);
    std::cout << "参照カウント:" << p1.use_count() << std::endl; // 1
    
    {
        std::shared_ptr<int> p2 = p1; // 共有、カウント+1
        std::cout << "参照カウント:" << p1.use_count() << std::endl; // 2
    } // p2が破棄、カウント-1
    
    std::cout << "参照カウント:" << p1.use_count() << std::endl; // 1
    
    return 0;
}
▶ 試してみよう

(2) 3.2 make_shared vs new

推奨: std::make_shared を使用、new は避ける。

特徴 new make_shared
例外安全 危険 安全
パフォーマンス 2回割り当て 1回割り当て
コード 長い 簡潔
CPP
// 推奨
auto p1 = std::make_shared<int>(42);

// 非推奨
std::shared_ptr<int> p2(new int(42));



4. weak_ptr

(1) 4.1 なぜweak_ptrが必要?

問題: shared_ptr循環参照は、メモリリークを引き起こします。

▶ サンプル:循環参照(難易度 ⭐⭐⭐)

CPP
#include <iostream>
#include <memory>

struct Node {
    std::shared_ptr<Node> next; // 危険!
    ~Node() { std::cout << "Node destroyed" << std::endl; }
};

int main() {
    auto n1 = std::make_shared<Node>();
    auto n2 = std::make_shared<Node>();
    
    n1->next = n2; // n1がn2を参照
    n2->next = n1; // n2がn1を参照(循環参照)
    
    return 0;
} // ❌ n1とn2が解放されない(メモリリーク)
▶ 試してみよう

(2) 4.2 weak_ptrで循環参照を解決

解決策: shared_ptrweak_ptr に変更。

CPP
struct Node {
    std::weak_ptr<Node> next; // weak_ptr、参照カウントを増やさない
    ~Node() { std::cout << "Node destroyed" << std::endl; }
};

int main() {
    auto n1 = std::make_shared<Node>();
    auto n2 = std::make_shared<Node>();
    
    n1->next = n2; // 参照カウントに影響なし
    n2->next = n1;
    
    return 0;
} // ✅ n1とn2が正常に解放される



5. スマートポインタの選び方

(1) 5.1 選び方の基準

使用場面 推奨
専有所有権 unique_ptr
共有所有権 shared_ptr
オブザーバー weak_ptr (非所有ポインタ)
配列 unique_ptr<T[]>

(2) 5.2 よくある間違い

エラー 説明
生ポインタでshared_ptrを作成 二重削除の危険
生ポインタとスマートポインタの混在 RAIIの利点を失う
手動でdeleteを呼ぶ スマートポインタが管理



6. 実践:スマートポインタでリソース管理

▶ サンプル 1:スマートポインタでデータベース接続管理(難易度 ⭐⭐⭐)

CPP
#include <iostream>
#include <memory>

// データベース接続クラス
class DatabaseConnection {
public:
    DatabaseConnection() {
        std::cout << "データベース接続を開きました" << std::endl;
    }
    
    ~DatabaseConnection() {
        std::cout << "データベース接続を閉じました" << std::endl;
    }
    
    void query(const std::string& sql) {
        std::cout << "SQL実行:" << sql << std::endl;
    }
};

int main() {
    // unique_ptrで管理
    std::unique_ptr<DatabaseConnection> conn(new DatabaseConnection());
    conn->query("SELECT * FROM users");
    
    return 0;
} // 自動的に接続を閉じる
▶ 試してみよう

▶ サンプル 3:unique_ptrのカスタムデリータ(難易度 ⭐⭐)

CPP
#include <iostream>
#include <memory>
#include <cstdio>

struct FileDeleter {
    void operator()(FILE* fp) const {
        if (fp) {
            fclose(fp);
            std::cout << "File closed automatically" << std::endl;
        }
    }
};

int main() {
    // unique_ptr with custom deleter for FILE*
    std::unique_ptr<FILE, FileDeleter> file(fopen("test.txt", "w"));

    if (file) {
        fprintf(file.get(), "Hello, Smart Pointer!");
    }

    // File automatically closed when file goes out of scope
    return 0;
}
▶ 試してみよう

出力:

TEXT 📖 参照専用
File closed automatically

❓ よくある質問

Q:unique_ptrとshared_ptrの使い分けは? A: unique_ptr は所有権が1つの場合(軽量)、shared_ptr は所有権を共有する場合(オーバーヘッドあり)。デフォルトは unique_ptr


Q:生ポインタを使う場面は? A: - 非所有アクセス(オブザーバー)ならOK

  • C言語APIとの連携
  • パフォーマンスが重要(ただし要注意)

Q:shared_ptrはスレッドセーフ? A:- 参照カウントの変更はスレッドセーフ

  • ただしオブジェクトへのアクセスはスレッドセーフではない(ミューテックス必要)

📖 まとめ

トピック 要点
unique_ptr 専有所有権、軽量
shared_ptr 共有所有権、参照カウント
weak_ptr 非所有参照、循環参照の解決
make_shared 推奨、例外安全
カスタムデリータ unique_ptrで使用、shared_ptrでも可能

📝 練習問題

  1. 初級(難易度 ⭐): unique_ptr で動的に確保した int を管理し、make_unique を使用。unique_ptrをコピーしようとして(コンパイルエラー)、ムーブを使用。

  2. 中級(難易度 ⭐⭐): shared_ptr で共有オブジェクトを実装。shared_ptrを複数作成し、use_count() を出力して参照カウントを観察。

  3. 上級(難易度 ⭐⭐⭐): weak_ptrshared_ptr の循環参照問題を解決。AクラスとBクラスが相互に shared_ptr を持ち、メモリリークを観察。weak_ptr で解決。



次のレッスン:テンプレートメタプログラミング(#47)

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%