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

リファレンスカウンタ方式でメモリ管理してくれるスマートポインタのメモ。cocos2d-x 3.0からC++11を使えるようになるらしいので、最近勉強してる。

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

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-Cのreleaseとは意味が違う。

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[i].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 を使うべきらしい。カスタムデリータで例外が発生してもメモリリークが発生しないとかなんとか。あと動作が高速らしい。

{
	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()[i].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

.swapでweak_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を使う。

  1. コメントはまだありません。

  1. トラックバックはまだありません。

*


Advertisement