C++ スマートポインタ std::shared_ptrの詳細

C++11で導入されたスマートポインタには、shared_ptrunique_ptrweak_ptrの3種類があります。

unique_ptrは所有権を独占するポインタであり、コピー構築子を持たず、所有権の移譲にはstd::moveを使用します。一方でweak_ptrは所有権を持たず、shared_ptrとの併用時に役立ち、特にマルチスレッド環境や循環参照の回避に利用されます。

shared_ptrは複数のポインタが同一オブジェクトを参照可能で、参照カウントが0になると自動的に破棄されます。get()メソッドで原始ポインタを取得でき、reset()で参照先を変更できます。

削除関数(deleter)はスマートポインタの破棄時に実行される関数で、unique_ptrshared_ptrで指定方法が異なります。unique_ptrは型を変更する必要がある一方、shared_ptrは初期化時に第二引数として指定可能です。

std::unique_ptr<FILE, decltype(&fclose)> fp(fopen("data.txt", "r"), &fclose);
auto resourceDeleter = [](Resource* pr) { ... };
std::shared_ptr<Resource> spr(new Resource, resourceDeleter);

上記コードではfpfcloseをバインドし、ファイルクローズを確実に実行しています。削除関数の型は管理対象オブジェクトのポインタと互換性がある必要があります。

shared_ptrの初期化には生ポインタを直接使用しないように注意が必要です。同じ生ポインタで複数のshared_ptrを生成すると、それぞれ独立した参照カウントが発生し、二重解放の原因になります。代わりにmake_sharedや右辺値による初期化が推奨されます。

auto p = new B();
std::shared_ptr<B> sp1(p);     // 避けたいパターン
std::shared_ptr<B> sp2(p);     // 異なるグループ!
std::shared_ptr<B> sp3(sp1.get());    // 異なるグループ!
// 推奨される初期化方法
std::shared_ptr<B> sp1(new B());
std::shared_ptr<B> sp1 = std::make_shared<B>();

クラス内で自身のshared_ptrを返す必要がある場合、shared_from_this()メソッドを使用する必要があります。これはstd::enable_shared_from_thisを継承することで実現でき、生ポインタの直接使用による二重解放を防ぎます。

class B : public std::enable_shared_from_this<B>
{
public:
    B()
    {
        std::cout << "B コンストラクタ" << std::endl;
    }

    ~B()
    {
        std::cout << "B デストラクタ" << std::endl;
    }

    std::shared_ptr<Data> createData(int id){
        std::shared_ptr<Data> dataPtr;
        dataPtr.reset(new Data(id), boost::bind(&B::deleteData, shared_from_this(), _1));    // shared_from_this()の使用
        return dataPtr;
    }
private:
    void deleteData(Data* d){
        ...
        delete d;
    }
};

上記例ではcreateDataメソッド内でreset()に削除関数を渡しながら、shared_from_this()で安全に自身のshared_ptrを取得しています。

タグ: shared_ptr unique_ptr weak_ptr deleter enable_shared_from_this

7月16日 19:24 投稿