...ing logging 4.0

はてなブログに移行しました。D言語の話とかいろいろ。

shared_ptrを使おう

当然だがほとんどの後輩がshared_ptrを知らないみたいなので,いつかC++講座で使うための説明用資料としてここに書いておく.

概要

 
shared_ptrを使うとnewして作成したオブジェクトを明示的にdeleteしなくてもよくなる.
 

原理的に,どこからも参照されなくなったオブジェクトにアクセスする方法は存在しないので,そのオブジェクトはもう破壊してしまっても問題がない.
shared_ptrは,オブジェクトが参照されている数をカウントしておいて(参照カウントと言う),参照カウントがゼロになるときに参照先のオブジェクトを1回だけdeleteしてくれる.
shared_ptrはauto_ptrと同じスマートポインタの一種であり,次期C++の標準ライブラリに導入される.それと同時にauto_ptrは非推奨となる.

サンプル

環境はSP1を適用済みのVS2008.
VS2008SP1を入れていれば本来次期C++に導入されるshared_ptrを今でも使うことができる.
なお,この環境においてshared_ptrはstd::tr1名前空間にあるが,次期標準規格でのstd名前空間にあるかのように使うために小細工をしている.

#include <memory> // std::tr1::shared_ptrを使えるようにする

namespace std {
	using namespace tr1; // std::tr1:: を std:: と書けるようにする
}

struct A {
	A() { printf("create\n"); } // 作られたことがわかるように
	~A() { printf("destroy\n"); } // 破壊されたことがわかるように

	void f() { printf("call\n"); } // 参照外し実験用
};

struct B {
	B() { printf("create\n"); } // 同上
	~B() { printf("destroy\n"); } // 同上

	std::shared_ptr<B> p; // 循環参照実験用
};

void main() {
	printf("--明示的なdelete呼び出し--\n");
	{
		A* x = new A; // 自分でnewすると
		x->f();
		delete x; // 1回だけdeleteしないといけない(2回したらダメ)

		// めんどくさいし難しいし忘れる!
	}
	printf("--shared_ptrで管理--\n");
	{
		std::shared_ptr<A> x(new A); // 裸のオブジェクトはすぐにshared_ptrに格納しよう
		// 作成したオブジェクトへの参照カウントは 1
		std::shared_ptr<A> y = x; // 同じ裸のオブジェクトを共有するとき
		// 共有したので参照カウントが1つ増えて 2 になる
		
		// 参照外しの方法はポインタと一緒
		x->f();
		y->f();
		// ここでスコープを抜けるときにxとyが破壊されて...
		// 同時に参照先のオブジェクトが1回だけdeleteされる!!
	}
	printf("--循環参照(1)--\n");
	{
		std::shared_ptr<B> s(new B); // ふたつのスマートポインタを作って
		std::shared_ptr<B> t(new B); // ...
		s->p = t; // お互いに...
		t->p = s; // お互いを参照し合うと
		// ここでどちらもdeleteされない
	}
	printf("--循環参照(2)--\n");
	{
		std::shared_ptr<B> s(new B);
		s->p = s; // 自分で自分を参照してても
		// やっぱりdeleteされない
	}
	// 循環参照になるときはweak_ptrを使う(発展課題)
}

// 定石(1)
typedef std::shared_ptr<A> A_Ptr; // 短い別名を付けて使う

// 定石(2)
A_Ptr createA() {
	return A_Ptr(new A); // 裸のポインタを極力嫌って shared_ptr で包んで返す
};

// どうしてもというなら...
void foo() {
	A_Ptr z(new A);
	A* w = z.get(); // 裸のポインタを取り出せる
	//でも自分でdeleteしてはいけません
};

実行結果

--明示的なdelete呼び出し--
create
call
destroy
--shared_ptrで管理--
create
call
call
destroy
--循環参照(1)--
create
create
--循環参照(2)--
create