【C++】std::shared_ptr の使い方 | 所有権を共有するスマートポインタ

目次

はじめに

C++の std::unique_ptr は、リソースの所有者が常に一人であることを保証するスマートポインタでした。しかし、時には、複数の場所から同じオブジェクトを共有して使いたい、という場面があります。

この「所有権の共有」を実現するのが、std::shared_ptr です。shared_ptr は、内部に「参照カウンタ」という仕組みを持っており、いくつの shared_ptr が同じオブジェクトを指しているかを数えています。そして、最後の shared_ptr が破棄され、参照カウンタがゼロになった瞬間に、オブジェクトのメモリを自動的に解放します。

この記事では、shared_ptr の主要な使い方を解説します。

  1. 基本的な使い方(参照カウント)
  2. 推奨される生成方法 (std::make_shared)
  3. リソースの再設定 (.reset)
  4. カスタムデリータの指定

【前提】C++11とは?

C++11は、2011年に正式化されたC++言語のメジャーアップデート版です。shared_ptr はこのC++11で導入されたため、利用するにはC++11以降に対応したコンパイラが必要です。


1. 基本的な使い方(参照カウント)

shared_ptr はコピーが可能で、コピーされるたびに参照カウンタが増加します。

サンプルコード

#include <iostream>
#include <memory> // shared_ptr
#include <string>

using namespace std;

struct Resource {
    string name;
    Resource(string n) : name(n) { cout << "リソース確保" << endl; }
    ~Resource() { cout << "リソース解放" << endl; }
};

int main() {
    shared_ptr<Resource> p1; // 空のshared_ptr
    
    cout << "--- ブロックに入ります ---" << endl;
    {
        shared_ptr<Resource> p2 = make_shared<Resource>("データA");
        cout << "参照カウント: " << p2.use_count() << endl; // -> 1
        
        p1 = p2; // コピー。p1とp2が同じオブジェクトを指す
        cout << "参照カウント: " << p2.use_count() << endl; // -> 2
    } // p2がスコープを抜けるが、p1がまだ所有しているのでリソースは解放されない
    cout << "--- ブロックを出ました ---" << endl;
    
    cout << "参照カウント: " << p1.use_count() << endl; // -> 1
    
    return 0;
    // main関数を抜ける際にp1が破棄され、参照カウントが0になるため、
    // ここで初めてリソースが解放される
}

2. std::make_shared による生成

shared_ptr を生成する際は、new を直接使うよりも、std::make_shared ヘルパー関数を使うことが強く推奨されます。make_shared は、オブジェクト本体と管理ブロック(参照カウンタなど)のメモリを一度に確保するため、より効率的で、例外安全性も高まります。

サンプルコード

// 非推奨な書き方
shared_ptr<Resource> p1(new Resource("データB"));

// 推奨される書き方 (C++14以降)
auto p2 = make_shared<Resource>("データC");

解説: make_shared には、生成したいクラスのコンストラクタに渡す引数をそのまま渡します。


3. リソースの再設定 (.reset)

.reset() を使うと、shared_ptr が現在指しているオブジェクトを解放し、新しいオブジェクトを指すように変更できます。引数なしで呼び出すと、ポインタは空になります。

auto p = make_shared<Resource>("データD");
cout << "参照カウント: " << p.use_count() << endl; // -> 1

p.reset(new Resource("データE")); // "データD" はここで解放される
cout << "reset後、pはデータEを指します。" << endl;

p.reset(); // "データE" はここで解放され、pは空になる
cout << "引数なしreset後、pは空です。" << (p ? "いいえ" : "はい") << endl;

4. カスタムデリータの指定

unique_ptr と同様に、delete 以外の方法でリソースを解放するためのカスタムデリータを指定できます。

#include <cstdio>

// FILE* を fclose するためのカスタムデリータ関数
void file_closer(FILE* fp) {
    if (fp) {
        fclose(fp);
        cout << "カスタムデリータ: ファイルを閉じました。" << endl;
    }
}

int main() {
    // shared_ptrのコンストラクタ第2引数に、デリータを渡す
    shared_ptr<FILE> file_ptr(fopen("test.txt", "w"), file_closer);

    if (file_ptr) {
        fwrite("hello", 1, 5, file_ptr.get());
    }
    // file_ptrが破棄される際に、自動的にfile_closer(fp)が呼ばれる
    return 0;
}

まとめ

今回は、C++11の std::shared_ptr を使った、所有権を共有するメモリ管理の基本を解説しました。

  • shared_ptr は、参照カウントを通じて、複数のポインタで一つのオブジェクトを安全に共有する。
  • 最後の所有者がいなくなった時点で、リソースは自動的に解放される。
  • 生成には、安全で効率的な make_shared を使うのが基本。
  • unique_ptr よりも少しオーバーヘッドがあるため、「所有権の共有が本当に必要か」を考えて使い分けることが重要。

shared_ptr は、複雑なオブジェクトの所有関係をシンプルかつ安全に表現するための、現代C++に不可欠なツールです。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次