並ゲームプログラマの書き捨て場

ゲームプログラマやってる私の日記、雑記、備忘録、公開場所

汎用型

前ブログの2014年11月01日の記事である。

 

 

僕は君でもあるんだよ?djannです。

 

私は比較的影響を受けやすい性質だ。最近、C++の深い話をしている記事等をよく見るのだが、いつものように簡単に影響を受けてしまい、その高度な記述を用いたライブラリの実装を行いたいと思う気持ちが強くなってしまった。

丁度boostのvariant型の話を読んだので、汎用型を作ってみた。

と言ってもboostのように高度なものではなく、色々と最低限な実装となる。メタでもない。

個人的には好きなのだが、使用法も、ユーザがサイズや現在格納されている型を意識する必要があるものとなっている(それに、規格違反の動作を利用している)。

 

// 作成中のライブラリ名.
namespace depot {


template < std::size_t size >
class var {
	char buf_[ size + 0x08 ];
	char *buf;

public:
	var() : buf( buf_ ){
		// アラインも指定できるようにしてしまおうか? - 現在標準で8バイト.
		if ( (reinterpret_cast<std::size_t>(buf) & 0x07) != 0 ){
			buf = buf + (0x08 - (reinterpret_cast(buf) & 0x07));
		}
	}
	// 同サイズのvarからの明確なコピーコンストラクタが必要(下はジェネリック過ぎる).
	var( const var<size> &rhs ) : var(){
		*this = rhs;
	}
	template <typename vtype>
	var( const vType &value ) : var(){
		*this = value;
	}
	template <typename vtype>
	vType &operator =( const vType &value ){
		// void*経由で警告を抑制する.
		const void *val = &value;
		std::memcpy( buf, val, sizeof(vType) );
		return *reinterpret_cast<vType*>(buf);
	}
	template <typename vtype>
	vType &operator =( const vType *value ){
		std::memcpy( buf, &value, sizeof(vType*) );
		return *reinterpret_cast<vType*>(buf);
	}
	// var同士の代入時はbufを書き戻さなければ
	// ひとまず違うサイズのvarならば動作は未定義(多分上記テンプレートが呼ばれる為.
	var &operator =( const var<size> &rhs ){
		// size以上の値設定は未定義動作 - アライン済みのbufからsize分コピーで完了.
		std::memcpy( buf, rhs.buf, size );
		return *this;
	}
	// ポインタに変換~等は用いないつもりでいた.
	template <typename vtype>
	operator vType&(){
		return *reinterpret_cast<vType*>(buf);
	}
};


// クラス等を用いる場合 - 使用中はvarが生きている必要がある.
template <class vtype>
class var_user {
	vType &val;
public:
	template <std::size_t size>
	explicit var_user( var &value ) : val( value ){}
	// コピーコンストラクタは封印.
	var_user( const var_user<vtype>& ) = delete;

	vType *operator ->(){ return &val; }
};
template <class vtype>
class var_user<vType*> {
	vType *&val;
public:
	template <std::size_t size>
	explicit var_user( var &value ) : val( value ){}
	// コピーコンストラクタは封印.
	var_user( const var_user<vType*>& ) = delete;

	vType *operator ->(){ return val; }
};


}	// namespace depot.

 

・使用例1

#include <iostream>
#include <variant>


int main()
{
	depot::var<16> v;
	// どれでも入れられる
	v = 1.529;
	v = 3.1415926535f;
	v = "It's string.";

	// 型情報は無いので、曖昧な場合が多く明示的なキャストは必須.
	std::cout << static_cast<const char>(v) << std::endl; // It's string.
}

 

・使用例2

#include 
#include <variant.h>


struct A {
	int a;
	virtual void foo(){
		std::cout << "This is fooooooo!! : " << a << std::endl;
	}
} a, *pa;
struct B : public A {
	void foo(){
		std::cout << "B is yeeeeeeeeeeeeeeeah!! : " << a << std::endl;
	}
} b;

int main()
{
	depot::var<16> v;
	v = a;
	// 構造体はuserを通して扱う.
	depot::var_user<a> vu_a( v );
	vu_a->a = 2000;
	vu_a->foo();    // This is fooooooo!! : 2000

	pa = &b;
	v = pa;
	// Aのポインタであることを意識しなければならない.
	depot::var_user<A*> vu_pa( v );
	vu_pa->a = 2500;
	vu_pa->foo();    // B is yeeeeeeeeeeeeeeeah!! : 2500
}    

 

泥臭い実装が故に、特に大きなオーバーヘッド等は無いはずである。

なお、var_userの方はvarよりテスト数が大幅に少なく、更に使用時は実体でもポインタでも、どちらもアロー演算子のお世話になることになる。

これは、実際にどちらで使っているかをユーザが勘違いしやすくなってしまうので、注意が必要だ。