📚

C++のスマートポインタとは何?まとめてみた

2022/10/20に公開

最近のC++でポインタというとスマートポインタがスタンダードだ。
自分は今まで古いC言語のポインタばかり使っていて、深くはスマートポインタを理解できていなかったので最近本腰入れて勉強しはじめた。
勉強したことをまとめてみた。

C++のスマートポインタの種類

以下の4つがすべてだが、auto_ptrはC++11から非推奨になっておりC++17では削除されるので実質残りの3つ。

  • auto_ptr -> C++17で削除
  • shared_ptr
  • unique_ptr
  • weak_ptr

スマートポインタとはそもそも何か

スマートポインタとな何かを一言でいうと従来のポインタをより使いやすくしたポインタである。
プログラムを書いていて最もバグが出やすいのはいつもポインタ周り。
メモリ解放忘れ、インクリメントやデクリメントミス、ポインタのコピーミス等ととても使いこなすのが難しい。
これらの使いにくさから解放してくれるのがスマートポインタ。

shared_ptr

shared_ptrは動的に確保したメモリの所有者を内部で管理することで、
所有者の数0になった時に自動的にメモリ解放してくれるスマートポインタ。
=でほかのshared_ptrにコピーすると所有者のカウントが増え、スコープを外れたりするとカウントが減る。

#include <iostream>
#include <memory>

int main()
{
    {
        std::shared_ptr<int> ptr(new int(100)); // 所有者+1
        {
            std::shared_ptr<int> ptr2;
            ptr2 = ptr; // 所有者+1
            std::cout << "*ptr2:" << *ptr2 << "\n";
        } // 所有者-1
        std::cout << "*ptr:" << *ptr << "\n";
    } // 所有者-1 (メモリ解放)
    return 0;
}

メモリ解放するためにわざわざdeleteをしなくてよいのはとてもラク。

unique_ptr

unique_ptrはその名の通りユニークでshared_ptrのように複数の所有者を持てないポインタ。
メモリは所有者が使い終わったら自動解放してくれる。

#include <iostream>
#include <memory>

int main()
{
    {
        std::unique_ptr<int> ptr(new int(100));
        std::unique_ptr<int> ptr2;  

        //ptr2 = ptr; // =でポインタコピーできない
        ptr2 = std::move(ptr); // std::moveで所有権を移せる

        std::cout << "*ptr2:" << *ptr2 << "\n";
    }// 使い終わったら自動でメモリ解放
    return 0;
}

=演算子でポインタコピーはできない仕様になっているが、std::moveを使うことでポインタの所有者を移すことができる。
move先の所有者がポインタを使い終わるとメモリは自動解放される。

weak_ptr

shared_ptrやunique_ptrと比較すると使用頻度は少ないスマートポインタ。
基本的にshared_ptrと組み合わせて使用するもので、shared_ptrの参照カウントを増やさずに監視ができる。

#include <iostream>
#include <memory>

int main()
{
    {
        std::shared_ptr<int> ptr(new int(100));
        std::weak_ptr<int> ptr2(ptr);

        std::cout << "参照カウント:" << ptr.use_count() << "\n"; // 参照カウント:1
        std::cout << "参照カウント:" << ptr2.use_count() << "\n"; // 参照カウント:1
        std::cout << "解放状態:" << ptr2.expired() << "\n"; // 0(未解放)
        std::cout << "*ptr:" << *ptr << "\n";
        ptr.reset();
        std::cout << "解放状態:" << ptr2.expired() << "\n"; // 1(解放済み)
    }
}

use_countメソッドでshared_ptrの参照数を監視でき、expiredメソッドで解放済みかどうかを確認できる。
監視だけしたい場合はこのweak_ptrを使うのがよい。

Discussion