D言語研究室



D言語用テンプレートライブラリ

singletonの実装 - vectorの実装(C++のstd::vectorのようなもの) - listの実装(C++のstd::listのようなもの) - objectPool

ここで考えたプログラムはYTL(yaneuraoTemplateLibrary)としてyaneSDK4Dに含めます。

singletonの実装

実際、ライブラリ等を作っているとsingletonクラスが必要になってきます。
yaneSDK3rdのほうでは、singletonをテンプレートで用意しており、これが非常に重宝していたのですが、これと同等のクラスをD言語で用意していかなくてはなりません。

まずは、オーソドックスな通常のsingletonパターンから行きましょう。


class singleton {
	static singleton get() {
		if (!me) {
			me = new singleton;
		}
		return me;
	}
private:
	this() {} // get以外での生成は困る
	static singleton me;
}

int main(char[][] args)
{
	singleton s = new singleton;	//	これコンパイル通るとマズー(゚Д゚)
	singleton s = singleton.get();	//	必ずこちらで生成


非publicなコンストラクタを持つクラスを外部からnew出来てしまうのはバグのようです。

それより重要なのは、signletonオブジェクトはdouble checked lockingパターンで書かなければならないということです。

double checked lockingパターンでは、二度チェックになって、二度目のチェックで同期オブジェクトを獲得する必要が出てきますが、その同期オブジェクト自体がすでに生成されている必要があります。これは、staticなthisコンストラクタを用いて同期用のオブジェクトを生成しておき、そのなかの関数をsynchronizedにしておけば良いでしょう。

そんなわけで、D言語用のdouble checked lockingを用いたsingletonテンプレートは以下のようになります。


template singleton(T) {
/**
    singletonテンプレート

    foo_classに対するsingletonなオブジェクトを作成するには、
        foo_class get_foo_class() {
            return instance singleton(foo_class).get();
        }
    のような取得関数を用意さえすれば、ok。
*/
	static T get() {
		if (!me) {
			my_mutex.mu.checkInstance(me);
		}
		return me;
	}
	private class my_mutex {
		synchronized void checkInstance(inout T me) {
		//	ここで同期オブジェクトを獲得する必要がある
			//	double checked locking
			if (!me) {
				me = new T;
			}
		}
		static this() { mu = new my_mutex; }
		//	スタティックコンストラクタにより起動時に自分自身を生成
		static my_mutex mu;
	}
	private static T me;
}


template内のstaticについては、つけなくても構わないのですが(→template構文)、将来的にこのへんの仕様、もしかしたら変更になるかも知れないのでつけてあります。(これはget関数と、変数meの前のstaticのこと。my_mutexクラス内のstaticは必要です。)

ところが現状、このsingletonテンプレートはうまく機能しません。原因は、他のモジュールのtemplate内のstatic constructorが呼び出されないということによるもので、どうしようか考え中です。

ところで、Javaのようなメモリモデルだとdouble checked lockingは害があるようです。D言語でも(volatileを付けたとしても)実装依存になる可能性はあります。

参考:
http://www-6.ibm.com/jp/developerworks/java/020726/j_j-dcl.html

そんなわけでしばらくはD言語ではdouble checked lockingは使わないほうが無難だと判断し、書き直したのがこれ。

template singleton(T) {
/**
    singletonテンプレート

    foo_classに対するsingletonなオブジェクトを作成するには、
        foo_class get_foo_class() {
            return instance singleton(foo_class).get();
        }
    のような取得関数を用意さえすれば、ok。
*/
	static T get() {
		my_mutex.mu.checkInstance();
		return my_mutex.me;
	}
	private class my_mutex {
		synchronized void checkInstance() {
		//	ここで同期オブジェクトを獲得する必要がある
			if (!me) me = new T;
		}
		//	スタティックコンストラクタにより起動時に自分自身を生成
		static this() { my_mutex.mu = new my_mutex; }
		static my_mutex mu;
		static T me;
	}
}


しかし、synchronized文を用いれば、ここで使用しているmy_mutexクラスに相当するものは不要になります。(synchronized文、D言語ではサポートされてないのかと勘違いしてました。ゴメンナサイ。)


template singleton(T) {
	static T get() {
		synchronized {
			if(!me) { me = new T; }
		}
		return me;
	}
	static T me;
} 


vectorの実装(C++のstd::vectorのようなもの)


いずれ、D言語に追加されるのかも知れませんが、それまでの繋ぎとして必要だと思いましたのでサクっと作りました。コピペしてご利用ください。

ここにあるのはサンプルコードで、yaneSDK4Dのytlのなかに含まれるものにはiterator,reverse_iterator等も実装してあります。yaneSDK4Dをdownloadしてご利用ください。


template vector(T) { class t {
/**
    C++のSTLのstd::vectorのようなクラス。

    使用例)
    instance vector(foo).t gg = new instance vector(foo).t;
    for(int i=0;i<       foo f = new foo;
        f.a = 20; f.b = 10;
        gg.push_back(f);
    }
    gg.getAt(15).a = 12;

    printf("%d %d \n",gg.getAt(25).a,gg.getAt(15).a);
*/

	T getAt(int n)	///	n番目の要素を取得。nは0〜size()-1まで
	{ return a_[n]; }

	int size()		/// vectorの要素数を返す
	{ return size_; }

	int capacity()	///	確保してあるvectorサイズ (2^nで確保)
	{
		if (!a_) return 0;
		return a_.length;
	}

/*  //  これ、DMDコンパイラでは未実装のようだ
    T opSlice(int n)    /// [ ] operator : n番目の要素を取得する
    { return getAt(n); }
*/

	void push_back(T t)	///	後ろに要素を追加
	{
		// 追加できるか?
		if (capacity() == size()) {
			//	駄目やん。再確保しる!
			int s = size()*2; // 現在のサイズの倍で再確保
			if (s == 0) {
				s++;
			}
			T[] a = new T[s];
			for(int i=0;i<size_;++i)
				a[i] = a_[i];	// これdeep copyにはならんのでそ?
			a_ = a;
		}
		a_[size_] = t;
		size_++;
	}

	void clear() /// 配列を空にする
	{
		size_ = 0;
		a_ = new T[16];	//	一応16個だけ確保しておくか..
	}

	bool empty() /// 配列は空か?
	{	return !(a_); }

	void resize(int n)
	/**
        サイズを変更する。size()は、ここで設定した値が返るようになる。
        ただし、いったんclearするので以前に格納していたものは、無くなる。
    */
	{
		clear();
		if (n==0) return ;
		size_ = n;
		//	これを2^nになるように繰り上げて確保
		int i=n-1,j=1;
		while (i!=0) { i>>=1; j<<=1; }
		a_ = new T[j];
	}

	this() { clear(); }

private:
	T[] a_;
	int size_;	//	サイズ
}}


listの実装(C++のstd::listのようなもの)


yaneSDK4Dのytl/listがそれです。downloadしてお使いください。
以下は使用例のみ掲載。


	C++のSTLのstd::listのようなクラス

	使用例)
	class foo {
		int a,b;
	}

	alias instance list(foo).t foolist;
	foolist l = new foolist;

	for(int i=0;i<5;++i){
		foo f = new foo;
		f.a = i; f.b = i*2;
		l.push_back(f);
		f = new foo;
		f.a = -i; f.b = -i*2;
		l.push_front(f);
	}
	printf("--start of printing\n");

	foreach(inout foo f;l){
		printf("%d,%d\n",f.a,f.b);
	}
	foreach(inout foo f,foolist.reverse r;l){
		printf("%d,%d\n",f.a,f.b);
	}
	foreach(int i,foo f,foolist.reverse r;l){
		//	この場合、i=n-1...0 になる
		printf("%d,%d\n",f.a,f.b);
	}

	printf("obverse\n");
	foolist.iterator it = l.begin();
	while(it!=l.end()) {
		printf("%d,%d\n",it().a,it().b);
		++it;
	}

	printf("reverse\n");
	foolist.reverse_iterator it2 = l.rbegin();
	while(it2!=l.rend()) {
		printf("%d,%d\n",it2().a,it2().b);
		++it2;
	}



objectPool


ゲーム中にnewを繰り返すと、GCに負荷がかかってよろしくないことは何度も書きました。そこで、事前にオブジェクトをnewして、オブジェクトが死ぬときには、解放はせず、プールに戻し、再利用する仕組みを考えます。

再利用するときには、reset関数を呼び出して内部状態を初期化します。これが、プールされるオブジェクトにとっては、これがコンストラクタ的なものだと考えれば良いでしょう。

オブジェクトをプールしておく仕組みというのは、一番単純な形ならば、「有限スタック」です。オブジェクトが必要になればスタックからひとつ取り出し、不要になればスタックに戻す。ただそれだけです。よって、配列をスタック的に利用して実装できます。


	alias objectPool!(Star) StarPool;
のようにして、
	//	☆オブジェクトを事前に生成しておく。
	StarPool pool = new StarPool;
	pool.setMax(500);
	foreach(inout Star s;pool) s = new Star;

プールからもらうときは、
	//	プールからオブジェクトをひとつもらう
	Star s = pool.pop();
	if (s) {
		s.reset(info,x,y);
		controller.addTask(s,0);
	}

プールに返すときは、
	if (time > 50){
		// 自殺する
		pool.push(this); // このオブジェクトをプールに戻す
		return 1;
	}
のようにする。


実装は以下のようになります。


template objectPool(T) {
class objectPool {
	///	poolするオブジェクトの最大数を設定する
	/**
        事前に設定すること。
        設定すると、以前保持していたオブジェクトは
        解放してしまうので注意。
    */
	void setMax(int n) {
		release();
		stack = new T[n];
	}

	void release() {
		if (stack) {
			//	stackの内容も巡回して明示的にdeleteするべきか?
			delete stack;
			stack = null;
		}
		sp = 0;
	}

	///	foreachに対応させるためのもの
	int opApply(int delegate(inout T) dg)
	{
		if (!stack) return 0;	//	ダメやん

		int result = 0;
		foreach(inout T t;stack) {
			result = dg(t);
			if (result)	break;
		}
		return result;
	}

	///	オブジェクトをひとつもらう
	/**
        未使用のオブジェクトがなければ、nullが返る。
    */
	T pop(){
		if (!stack) return null;
		if (sp==stack.length) return null;
		return stack[sp++];
	}

	///	オブジェクトをひとつ返す
	/**
        最初setMaxで確保して、ここからpopで取得した分以外の
        オブジェクトを返してはならない。
    */
	void push(T t){
		if (!stack) return ;
		if (sp==0) return ; // errorであるべきか..
		stack[--sp] = t;
	}

	/// 未使用のオブジェクト数を返す
	int getRest() { return stack.length - sp; }

private:
	T[] stack;
	int sp; // stack pointer
}}


yaneSDK4Dのsample3a,bがオブジェクトプールのサンプルとなっていますので、そちらも参考にしてください。


Last Updated : 2004-4-19

written by yaneurao
http://bm98.yaneu.com/dlang/