C++11のスマートポインタ

前提

  • C++のnewはJavaC#のnewのようなつもりで気安く書いてはいけない。
  • なぜならC++には言語レベルではガベージコレクタが無い。
  • newしたオブジェクトは自己責任でdeleteしなければならない。
  • 自動解放の仕組みをライブラリレベルで提供しているのがスマートポインタ。
  • スマートポインタにもいろいろある。

スマートポインタいろいろ

  • std::auto_ptr : C++98からあった。いろいろ欠点があり、C++11では非推奨。
  • std::unique_ptr : std::auto_ptrの改良版のようなやつ。C++11ではこちらを使うべし。
  • std::shared_ptr : 一つのオブジェクトを複数のスマートポインタから参照できる。
  • std::weak_ptr : 弱参照する。std::shared_ptrだと循環参照に陥る場合とかに用いる。
  • boost::scoped_ptr : std::unique_ptrの前身? しらんけど。
  • boost::shared_ptr : std::shared_ptrの前身? しらんけど。
  • boost::weak_ptr : std::weak_ptrの前身? しらんけど。

C++11使えること前提なら、std::unique_ptrstd::shared_ptr、おまけでstd::weak_ptrをおさえておけばよい。オブジェクトの所有権を独占する(std::unique_ptr)か共有する(std::shared_ptr)に明示的な区別があるのがC++のスマートポインタの特徴。JavaC#の参照にはそのような概念はない。

std::unique_ptr

  • いちばん基本的なスマートポインタ
  • オブジェクトの所有権を独占する。
  • つまり、あるスマートポインタのスコープから出るときにオブジェクトは必ず解放される。
  • そのため参照のコピーは出来ないが、所有権を譲渡することはできる。
サンプルコード
#include <iostream>
#include <memory> // スマートポインタを使うために必要

// テキトーなクラス
class Hoge
{
public:
    Hoge()
    {
        std::cout << "constructor" << std::endl;
    }
    ~Hoge()
    {
        std::cout << "destructor" << std::endl;
    }
    void message()
    {
        std::cout << "message" << std::endl;
    }
};

int main(void)
{
    // 生ポインタの場合
    std::cout << "Raw Pointer:" << std::endl << std::endl;
    for (int i = 0; i < 3; ++i)
    {
        Hoge* hoge = new Hoge();
        hoge->message();
    }

    // スマートポインタの場合
    std::cout << std::endl << "Smart Pointer:" << std::endl << std::endl;
    for (int i = 0; i < 3; ++i)
    {
        std::unique_ptr<Hoge> hoge(new Hoge()); // 定義の記述はちょっとウザいが
        hoge->message(); // 生ポインタと同様の記述で使える
    }

    return 0;
}
実行結果

生ポインタだとデストラクタは呼ばれないが、スマートポインタだと呼ばれる。

Raw Pointer:

constructor
message
constructor
message
constructor
message

Smart Pointer:

constructor
message
destructor
constructor
message
destructor
constructor
message
destructor


std::shared_ptr

  • 一つのオブジェクトを複数のポインタから参照したいときに使うスマートポインタ
  • オブジェクトの所有権を共有する。
  • そのために、オブジェクトを参照しているポインタの数が参照カウンタで監視されている。
  • 参照カウンタがゼロになったときにオブジェクトは解放される。
サンプルコード
#include <iostream>
#include <string>
#include <memory>

// テキトーなクラス
class Hoge
{
public:
    Hoge(std::string name)
    {
        m_name = name;
        std::cout << "constructor of " << m_name << std::endl;
    }
    ~Hoge()
    {
        std::cout << "destructor of " << m_name << std::endl;
    }
    void message()
    {
        std::cout << "I am " << m_name << std::endl;
    }
private:
    std::string m_name;
};

int main()
{
    // std::unique_ptrの場合
    std::cout << "std::unique_ptr:" << std::endl;
    {
        std::unique_ptr<Hoge> u1(new Hoge("u1"));
        u1->message();
//      std::unique_ptr<Hoge> u2 = u1; ←コンパイルエラーになる (参照のコピー不可)
    }

    // std::shared_ptrの場合
    std::cout << std::endl << "std::shared_ptr:" << std::endl;
    {
        std::shared_ptr<Hoge> s1(new Hoge("s1"));
        s1->message();
        {
            std::shared_ptr<Hoge> s2 = s1; // 参照のコピー可
            s2->message();
            std::cout << "s2's scope ends" << std::endl;
        }
        std::cout << "s1's scope ends" << std::endl;
    }
    return 0;
}
実行結果
std::unique_ptr:
constructor of u1
I am u1
destructor of u1

std::shared_ptr:
constructor of s1
I am s1
I am s1
s2's scope ends
s1's scope ends
destructor of s1



std::weak_ptr

  • 参照するだけで所有権には関与しない。
  • つまり参照カウンタの増減に影響しない。
  • したがって、このポインタが生存中でも、オブジェクトは既に解放されているかもしれない。
  • 生ポインタとちがって、オブジェクトが解放されたかをチェックすることができる。
  • std::shared_ptrだと循環参照に陥る場合とかに用いる。
サンプルコード
include <iostream>
#include <string>
#include <memory>

// テキトーなクラス
class Hoge
{
public:
    Hoge(std::string name)
    {
        m_name = name;
        std::cout << "constructor of " << m_name << std::endl;
    }
    ~Hoge()
    {
        std::cout << "destructor of " << m_name << std::endl;
    }

    std::shared_ptr<Hoge> s_ptr;
    std::weak_ptr<Hoge> w_ptr;

private:
    std::string m_name;
};

int main()
{
    // 循環参照の例
    std::cout << "Circular Reference" << std::endl;
    {
        std::shared_ptr<Hoge> s1(new Hoge("s1"));
        std::shared_ptr<Hoge> s2(new Hoge("s2"));
        s1->s_ptr = s2;
        s2->s_ptr = s1;
    }

    // 弱参照の例
    std::cout << std::endl << "Weak Reference" << std::endl;
    {
        std::shared_ptr<Hoge> s1(new Hoge("s1"));
        std::shared_ptr<Hoge> s2(new Hoge("s2"));
        s1->s_ptr = s2;
        s2->w_ptr = std::weak_ptr<Hoge>(s1); // 片方を弱参照にする
    }
    return 0;
}
実行結果

循環参照だとオブジェクトが解放されずメモリリークするが、片方が弱参照だと解放される。

Circular Reference
constructor of s1
constructor of s2

Weak Reference
constructor of s1
constructor of s2
destructor of s1
destructor of s2