スマートポインタ深掘り

unique_ptr と shared_ptr 使い分ける。

概要

Modern C++では、raw ポインタではなく スマートポインタを使用します。

std::unique_ptr:単独所有
std::shared_ptr:複数所有

std::unique_ptr: 単独所有

unique_ptr は、 メモリを単独で所有します。 所有権の移譲のみ可能。

#include <iostream>
#include <memory>

class Resource {
public:
    void use() { std::cout << "使用中" << std::endl; }
    ~Resource() { std::cout << "破棄" << std::endl; }
};

int main() {
    std::unique_ptr<Resource> ptr1 = std::make_unique<Resource>();
    ptr1->use();
    
    // 所有権を移譲
    std::unique_ptr<Resource> ptr2 = std::move(ptr1);
    ptr2->use();
    
    // ptr1 はもう使えない
    // ptr1->use();  // エラー! nullptr になっている
    
    return 0;
}

使用中
使用中
破棄

std::unique_ptr の配列

#include <iostream>
#include <memory>

int main() {
    std::unique_ptr<int[]> arr = std::make_unique<int[]>(5);
    
    for (int i = 0; i < 5; i++) {
        arr[i] = i * 10;
        std::cout << arr[i] << " ";
    }
    std::cout << std::endl;
    
    return 0;
}

0 10 20 30 40

unique_ptr<int[]>を使えば、 delete[]を忘れません。

std::shared_ptr: 複数所有

shared_ptr は、 メモリを複数で共有します。 参照カウント方式で自動破棄。

#include <iostream>
#include <memory>

class Resource {
public:
    void use() { std::cout << "使用中" << std::endl; }
    ~Resource() { std::cout << "破棄" << std::endl; }
};

int main() {
    std::shared_ptr<Resource> ptr1;
    {
        std::shared_ptr<Resource> ptr2 = std::make_shared<Resource>();
        ptr1 = ptr2;  // 参照カウント +1
        
        ptr2->use();
        std::cout << "参照カウント: " << ptr1.use_count() << std::endl;
    }  // ptr2 のスコープ離脱、参照カウント -1
    
    ptr1->use();
    std::cout << "参照カウント: " << ptr1.use_count() << std::endl;
    
    return 0;
}

使用中
参照カウント: 2
使用中
参照カウント: 1
破棄

shared_ptr の参照カウント

#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> ptr1 = std::make_shared<int>(42);
    std::cout << "カウント: " << ptr1.use_count() << std::endl;
    
    {
        std::shared_ptr<int> ptr2 = ptr1;
        std::cout << "カウント: " << ptr1.use_count() << std::endl;
        
        std::shared_ptr<int> ptr3 = ptr1;
        std::cout << "カウント: " << ptr1.use_count() << std::endl;
    }
    
    std::cout << "カウント: " << ptr1.use_count() << std::endl;
    std::cout << "値: " << *ptr1 << std::endl;
    
    return 0;
}

カウント: 1
カウント: 2
カウント: 3
カウント: 1
値: 42

unique_ptr vs shared_ptr

項目 unique_ptr shared_ptr
所有 単独 複数
オーバーヘッド 最小 参照カウント
コピー 不可 可能
移譲 std::move 通常でOK

デフォルト:unique_ptr を使う。 複数所有が必須の場合のみ shared_ptr。

実践例:リスト管理

#include <iostream>
#include <memory>
#include <vector>

struct Item {
    std::string name;
    explicit Item(const std::string& n) : name(n) {}
    ~Item() { std::cout << "削除: " << name << std::endl; }
};

int main() {
    std::vector<std::unique_ptr<Item>> items;
    
    items.push_back(std::make_unique<Item>("りんご"));
    items.push_back(std::make_unique<Item>("みかん"));
    items.push_back(std::make_unique<Item>("バナナ"));
    
    for (auto& item : items) {
        std::cout << item->name << std::endl;
    }
    
    return 0;
}

りんご
みかん
バナナ
削除: バナナ
削除: みかん
削除: りんご

ポイント

  • raw ポインタを避ける
  • デフォルト:unique_ptr(単独所有)
  • 複数所有:shared_ptr(参照カウント)
  • std::make_unique/make_sharedを推奨

まとめ

  • スマートポインタで自動メモリ管理
  • unique_ptr は効率的、速度重視
  • shared_ptr は柔軟、複数所有対応