[C++11] スマートポインタ - unique_ptr, shared_ptr, weak_ptr

スマートポインタのつかいかた・サンプルコード

参考

テスト用に簡単なクラスを用意しておく

class Test {
 private:
  int tag;

 public:
  Test() {
    static int sTag = 0;
    this->tag = --sTag;
    printf("Constructor %d\n", tag);
  }

  Test(int tag) {
    this->tag = tag;
    printf("Constructor %d\n", tag);
  };

  ~Test() {
    printf("Destructor %d\n",  tag);
  };

  void print() {
    printf("TEST %d\n", tag);
  };
};

std::unique_ptr

唯一の所有者となるポインタ

{
  std::unique_ptr<Test> unique_p(new Test(0));
  unique_p->print();
}

結果

Constructor 0
Test 0
Destructor 0
{
  // unique_ptrは他の変数に持たせられない
  std::unique_ptr<Test> unique_p(new Test(0));

  std::unique_ptr<Test> up1 = unique_p; // コンパイルエラー
  std::unique_ptr<Test> up2(unique_p); // コンパイルエラー
  std::shared_ptr<Test> sp(unique_p); // コンパイルエラー
  std::weak_ptr<Test> wp(unique_p); // コンパイルエラー
}

.swapで交換できるし、 std::move で別の変数に移譲もできる

「unique_ptrは他の変数に持たせられない」とか書いたけどできなくもない。

{
  std::unique_ptr<Test> unique_p0(new Test(0));
  std::unique_ptr<Test> unique_p1(new Test(1));

  unique_p0.swap(unique_p1);
  unique_p0->print(); // TEST1
  unique_p1->print(); // TEST0

  unique_p1.reset();
  unique_p1 = std::move(unique_p0);
  unique_p1->print(); // TEST1
}

結果

Constructor 0
Constructor 1
TEST 1
TEST 0
Destructor 0
TEST 1
Destructor 1

.get で所有しているポインタを取得

取得は出来るけどそのポインタを削除すると2重解放になって死ぬ。

{
  // 2重解放されるのでNGな例
  Test *p = new Test(0);
  std::unique_ptr<Test> unique_p(p);
  assert(p == unique_p.get()); // getでunique_ptrが所有するポインタが取れる
  delete p;
}

結果

Constructor 0
Destructor 0
Destructor 0
malloc: *** error for object 0x100114900: pointer being freed was not allocated

.release で所有権を破棄

所有権を破棄して管理しているポインタは捨てる(ので NULL になる)。

でも delete はしないので注意。Objective-Crelease とは意味が違う。

Test *p = new Test(0);
{
  std::unique_ptr<Test> unique_p(p);
  // releaseで所有権を破棄
  unique_p.release();
  // releaseするとポインタはNULLになる : 破棄した訳ではない
  puts(unique_p.get() == nullptr ? "NULL" : "x");
}
p->print();
delete p;

結果

Constructor 0
NULL
TEST 0
Destructor 0

.reset

ポインタを持ち直したり、完全破棄したりできる。

{
  // resetでポインタを持ち直せる
  // 以前に持っていたオブジェクトはdelete
  std::unique_ptr<Test> unique_p(new Test(0));
  unique_p->print();
  unique_p.reset(new Test(1));
  unique_p->print();
}

結果

Constructor 0
TEST 0
Constructor 1
Destructor 0
TEST 1
Destructor 1
{
  // resetに引数指定しない場合は所有オブジェクトをdelete
  std::unique_ptr<Test> unique_p(new Test(0));
  unique_p->print();
  unique_p.reset();
  puts(unique_p.get() == nullptr ? "NULL" : "x");
  unique_p.release(); // NULLでもreleaseしても問題ないっぽい
}

結果

Constructor 0
TEST 0
Destructor 0
NULL

unique_ptr で動的配列を扱う

shared_ptr と違い特殊化されているためそのまま問題なく利用可能

{
  std::unique_ptr<Test[]> unique_p(new Test[3]);

  for (int i = 0; i < 3; i++) {
    unique_p[].print();
  }
}

結果

Constructor -1
Constructor -2
Constructor -3
TEST -1
TEST -2
TEST -3
Destructor -3
Destructor -2
Destructor -1

※配列範囲外を指定してもエラーが発生するとは限らないので注意。通常のC言語配列と同じ挙動?

shared_ptr も動的配列を扱えるけど unique_ptr とは使い方が異なる。

operator bool const

を実装してるので get でポインタ取り出さなくても条件判定できる

{
  std::unique_ptr<Test> unique_p(new Test);
  if (unique_p)
  {
    puts("NOT NULL");
  }

  unique_p.reset(nullptr);
  if ( ! unique_p)
  {
    puts("NULL");
  }
}

結果

Constructor -1
NOT NULL
Destructor -1
NULL

std::shared_ptr

std::shared_ptr は強参照のポインタ。

ただこいつはちょっと遅いらしい。

{
  std::shared_ptr<Test> shared_p1(new Test(0));
  printf("%ld\n", shared_p1.use_count()); // 1 - use_countで参照カウント取得
  {
    std::shared_ptr<Test> shared_p2 = shared_p1; // 代入演算子を使う
    printf("%ld\n", shared_p1.use_count()); // 2
  }
  printf("%ld\n", shared_p1.use_count()); // 1
}

結果

Constructor 0
1
2
1
Destructor 0
// これは落ちる
{
  std::shared_ptr<Test> shared_p1(new Test(0));
  std::shared_ptr<Test> shared_p2(shared_p1.get()); // これはダメ
  printf("%ld\n", shared_p1.use_count()); // 1

  // shared_ptr に release メソッドがあれば
  // クラッシュさせずに終わらせることも出来るけど無い。
  // まぁそんなアホな真似しても仕方ないのでイランけど。
}

結果

Constructor 0
1
Destructor 0
Destructor 0
malloc: *** error for object 0x100114910: pointer being freed was not allocated

std::make_shared

参考サイトによると std::shared_ptr の生成には std::make_shared を使うべきらしい。 カスタムデリータで例外が発生してもメモリリークが発生しないとかなんとか。あと動作が高速らしい。

[追記] ただ make_shared は万能ではないとのこと… - make_shared で確保されたメモリ領域は,それを参照する weak_ptr が無くならない限り解放されない - 野良C++erの雑記帳

{
  std::shared_ptr<Test> shared_p1 = std::make_shared<Test>();
  std::shared_ptr<Test> shared_p2 = std::make_shared<Test>(0);
}

結果

Constructor -1
Constructor 0
Destructor 0
Destructor -1
// 最初間違えてこんな書き方してしまった…
// これだとTestクラスのコピーコンストラクタが呼ばれる。
// Testクラスがムーブコンストラクタを実装してれば
// ムーブコンストラクタを呼んでくれるので
// その場合はたいしたオーバーヘッドは発生しないけど…
{
  std::shared_ptr<Test> shared_p1 = std::make_shared<Test>(Test(0));
}

std::make_shared - cppreference.com

.unique() で一意かどうかをチェック

use_count == 1 の結果を返す。

{
  std::shared_ptr<Test> shared_p;
  if (shared_p == nullptr)
  {
    printf("Use Count = %ld\n", shared_p.use_count());
    shared_p = std::make_shared<Test>(5);
    if (shared_p.unique())
    {
      auto shared_p2 = shared_p;
      printf("Use Count = %ld\n", shared_p.use_count());
    }
  }
}

結果

Use Count = 0
Constructor 5
Use Count = 2
Destructor 5

shared_ptr で動的配列を扱う

{
  std::shared_ptr<Test> pArray(new Test[3], std::default_delete<Test[]>());
  for (int i = 0; i < 3; i++)
  {
    pArray.get()[].print();
  }
}

結果

Constructor -1
Constructor -2
Constructor -3
TEST -1
TEST -2
TEST -3
Destructor -3
Destructor -2
Destructor -1

std::weak_ptr

弱酸性のポインタ。

use_count() で参照カウントを取れるのは shared_ptr と同じ。

.unique() は無いけど .expired() がある。

これは use_count() == 0 を返してくるだけ。

{
  std::weak_ptr<Test> weak_p;
  {
    std::shared_ptr<Test> shared_p1 = std::make_shared<Test>(7);
    weak_p = shared_p1;
    printf("Use Count = %ld\n", weak_p.use_count());

    // expired() でポインタが解放されたかどうか判定可能
    if ( ! weak_p.expired())
    {
      // weak_p->print() のように直接関数は呼び出し不可
      // 利用時は lock() で shared_ptr を取得して使う。
      std::shared_ptr<Test> shared_p2 = weak_p.lock();
      printf("Use Count = %ld\n", weak_p.use_count());
    }

    printf("Use Count = %ld\n", weak_p.use_count());
  }

  printf("Use Count = %ld\n", weak_p.use_count());
  puts(weak_p.expired() ? "expired" : "unexpired");
}

結果

Constructor 7
Use Count = 1
Use Count = 2
Use Count = 1
Destructor 7
Use Count = 0
expired
{
  std::shared_ptr<Test> shared_p1 = std::make_shared<Test>(7);
  std::weak_ptr<Test> weak_p = shared_p1;

  std::unique_ptr<Test> unique_p(new Test);
  weak_p = unique_p; // コンパイルエラー : unique_ptrは受け取れない
  weak_p.get(); // コンパイルエラー
  weak_p.reset(shared_p1); // コンパイルエラー
  weak_p->print(); // コンパイルエラー
}

.reset でポインタ管理放棄

{
  std::shared_ptr<Test> shared_p1 = std::make_shared<Test>(7);
  std::weak_ptr<Test> weak_p = shared_p1;
  weak_p.reset(); // ポインタ管理放棄
  printf("%ld\n", weak_p.use_count()); // 0
  auto shared_p2 = weak_p.lock();
  puts(shared_p2 ? "x" : "null"); // null
}

結果

Constructor 7
0
not null
Destructor 7

.swapweak_ptr の入れ替え

{
  std::shared_ptr<Test> shared_p1 = std::make_shared<Test>(1);
  std::shared_ptr<Test> shared_p2 = std::make_shared<Test>(2);
  std::weak_ptr<Test> weak_p1 = shared_p1;
  std::weak_ptr<Test> weak_p2 = shared_p2;
  weak_p1.swap(weak_p2);
  weak_p1.lock()->print(); // TEST 2
  weak_p2.lock()->print(); // TEST 1
}

結果

Constructor 1
Constructor 2
TEST 2
TEST 1
Destructor 2
Destructor 1

std::auto_ptr

これ昔から存在するけど使わないほうがよくて、C++11 では替わりに std::unique_ptr を使う。

Share
関連記事