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

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

unique_ptrでrange-based-for

君の領地はここからここまでだ。djannです。

 

スタック・オーバーフローを見ていたところ、unique_ptr<T[]>でrange-based-forを用いたいという質問があった。というわけで早速作ってみた。

 


#include <iostream>
#include <memory>


// unique_wrapperとmake_unique_wrapperを作るためのヘルパ - かなり手抜きその1.
// 配列か判定する - 結果はvalueが1 or 0で判別.
template <class vType> struct is_array {
	enum { value = 0 };	// falseである.
};
template <class vType> struct is_array<vType[]> {
	enum { value = 1 };	// trueである.
};
// trueのときだけ型が存在する - SFINAEに用いる.
template <bool Condition, class vType>
struct condition_type {};
template <class vType>
struct condition_type<true, vType> {
	using type = vType;
};
// 型から配列要素を抜く - 配列じゃなければ元のままに.
template <class vType>
struct remove_array {
	using type = vType;
};
template <class vType>
struct remove_array<vType[]> {
	using type = vType;
};

// 通常版と配列版を同時にラッピングしている分、若干危険かもしれない・・・?.
template <class vType>
class unique_wrapper : public std::unique_ptr<vType> {
	std::size_t num_array;
	using MyType = unique_wrapper<vType>;
	using PtrType = typename remove_array<vType>::type;
public:
	unique_wrapper(MyType const &) = delete;
	// 1個版と配列版と両方のコンストラクタを持つ - make_unique_wrapperのfriendにしてprivate化するか?.
	unique_wrapper(PtrType *ptr) : std::unique_ptr<vType>(ptr), num_array(1){}
	unique_wrapper(PtrType *ptr, std::size_t num) : std::unique_ptr<vType>(ptr), num_array(num){}
	unique_wrapper(MyType &&rhs) : std::unique_ptr<vType>(rhs.release()), num_array(rhs.num_array){}
	
	// 最低限このようなインターフェースを持つiteratorと
	class iterator {
		PtrType *ptr_;
	public:
		iterator(PtrType *ptr) : ptr_(ptr){}
		~iterator(){}

		PtrType *operator ++(){ ++ptr_; return ptr_; }
		PtrType &operator *(){ return *ptr_; }
		bool operator !=(iterator rhs){ return *ptr_ != *rhs; }
	};
	// 上記を戻すbeginとendというインターフェースがあればrange-based forは動く.
	iterator begin(){ return this->get(); }
	iterator end(){ return this->get() + num_array; }
};

// make_unique_wrapper自体の各種 - かなり手抜きその2.
template <class vType>
unique_wrapper<typename condition_type<is_array<vType>::value == 1, vType>::type> make_unique_wrapper(std::size_t n){
	unique_wrapper<vType> wrap(new typename remove_array<vType>::type[n], n);
	return wrap;
}
template <class vType, class ...Args>
unique_wrapper<typename condition_type<is_array<vType>::value == 0, vType>::type> make_unique_wrapper(Args... args){
	unique_wrapper<vType> wrap(new vType((args)...));
	return wrap;
}


struct Foo {
	~Foo(){
		std::cout << "Foo destructor." << std::endl;
	}
	void print(){
		std::cout << "My name is Foo." << std::endl;
	}
};


int main()
{
	// use test code.
	{
		unique_wrapper<int[]> ptr = make_unique_wrapper<int[]>(7);
		{
			for (auto &n : ptr){
				std::cout << "Please number and enter : ";
				std::cin >> n;
			}
		}

		for (auto n : ptr){
			std::cout << n << std::endl;
		}
		std::cin.get();
		std::cin.get();
	}	// この段階でちゃんとdelete[]が働く.
	{
		// 折角なんでFooさんでテスト.
		unique_wrapper<Foo> ptr = make_unique_wrapper<Foo>();
		ptr->print();
	}

	std::cin.get();
}


 

大事なのは、unique_wrapper::iteratorのoperator overload群とunique_wrapper::beginとunique_wrapper::endを用意することで、後は何も気にせずにrange-based-forに投げ込めるということだ。

現在のC++は標準ライブラリだけでなく、言語機能にもテンプレートが深く入り込んでいるが、このように「特定のインターフェースが存在すれば、派生関係がなくとも同様の機能が使用できる」というのが、実にC++らしくまた、拡張性もよく型安全ないいところだろう。

C++0xの頃、このような特定のインターフェースの群をコンセプトとしてまとめて扱う提案があったという。残念ながら一旦保留という形になったが、今でもまだ議論は続き、いずれC++の標準として定義されると思われる。

 

※ちなみにunique_wrapper::iteratorだが、実は必要ない。というのも、ポインタには各operator++, operator*, operator!=がそれぞれ元から使用可能だからだ。そうはいかないクラスをrange-based-forで用いたいと思った際のたたき台として、今回はわざわざiteratorを用意することにした。

memset至上主義

これこそが!世のすべてなのだぁぁ!!djannです。

 

私の職場では現在、とりあえず複合型に対してmemsetを行って初期化をするという事が横行している。まあPOD、standard-layout-classであれば問題ない(と言っても、実際には特定のデータメンバは未使用値が0ではない等があり問題となる)のだが、実際のところ完全にそうだと保証できない、非standard-layout-classであり、もちろん非PODであり、非trivially-copyable-classである場合が多々存在する。

memsetでの初期化を行う専用に、新たにMemoryClear的なマクロ(フリー関数ではない。マクロなのだ)を作成している程の有り様だ。

 

さて、聡明な諸君なら周知の事実だが、複合型の初期化は本来、以下のように記述することが望ましい。

// 何らかの複合型T.
T t = {};

こう記述することにより、環境に応じた正しい「0」が各データメンバに格納される。例えば浮動小数点数の0は、必ず全ビットが0とは限らない。そういった場合でも、必ず表現としての0が格納される。

 

現在memsetを用いて0埋めを行っている状況で、どのような代替手段を用意すればこれを是正できるか。前述の通り、MemoryClear的なマクロが存在するのだが、そちらでは引数がひとつだけになっている。おおよそ他の引数を記述するのが面倒、少しでも記述量を減らしたいという、実にプログラマ的な考えからそれが生まれたのだろう。

ではその考えに則って、そのMemoryClearを代替するフリー関数を用意しよう。要件は、引数が初期化したい複合型のオブジェクトひとつ、渡された複合型を正しい形で初期化できる。以上の2点だ。

template <class T>
T const &compound_initialize( T &t_ ){
    t = {};
    t_ = t;
    return t_;
}

引数のlvalue referenceを戻すことに特に意味はない。memset等の流儀に則り、destination引数を戻すようにしているだけだ。

なお、このままだと配列に対してはコンパイルエラーになってしまうので、配列専用のオーバーロードを用意すべきだろう。

template <class T, std::size_t N>
T const *compound_initialize( T (&t_)[N] ){
    T t = {};
    std::fill_n( t_, N, t );
    return t_;
}

さてこれでMemoryClearマクロを置き換える準備が整った。少なくともmemsetを使い仮想関数を壊す、内部的に保持されているアドレス、数量等を破壊するようなことはなくなるだろう。ちゃんと使われれば。

 

なお、巨大なストレージを確保する非trivially copyable classなどでコストが心配な場合は、rvalue referenceを受け取るコンストラクタを定義した上でstd::moveまたはstd::forwardを適切に用いるように変更するといいかもしれない。

STLであればほとんどrvalue referenceを受け取るムーブイディオムには対応していると思われる(標準ライブラリはほとんど追っていない)。

 

てすと

あいうえお*1

 

前回のweak attributeについて書いている際に、気付いたら※が出来、下に注釈として内容の一部が飛んでいた。

どうやらそういう記法だったらしい。()で二重に囲むと、その内部が記事の下部に解説として出現、リンクが自動で貼られるらしい。

特別な機能、記法に興味が無かったので特に調べてはいなかったが、そういうものもあるのだな。

 

と思ったら、[]を二重で囲む方もどうやら何らかの機能があるみたいだ。preの内部でHTML編集を行っていても消えてしまっていた。

ひとまず妥協として[ [attribute-name] ]と書いてあるが、実際にスペースを開けてもいいのかは分からない。

 

せめてpreの中では機能をオフにする必要がありそうだ(出来ればコードの構文強調も行いたい)。ちょっと調べる必要がありそうだ。

 HTMLには特に明るくなく、単にpreで改行やスペース等がそのまま出るという程度の認識で使っていたが、このままでは良く無さそうだ。

*1:これは、注釈のテストとなります

weak attributeが欲しい

弱い・・・弱すぎるぞ!!djannです。

 

単なる願望なのだが、C++の標準にweak attributeが欲しい。特別どうしてもこれが無ければどうしようもない!首を吊るしかない!というような強い要望という訳ではなく、あったら便利かもしれない程度のものだが。

私が初めて目にしたのは、とあるSDKを利用する際に、使用可能なヒープのサイズをweak attributeで修飾されたグローバル変数を用いていた場面だ。そこから調べ、特定のコンパイラの拡張だと知った。リファレンスを読んだりした訳ではないが、大体以下のような機能を持っている。

 

・ソースファイルAで変数(関数も?)にweak指定を行う(int a __attribute__( (weak) );のような形だったか?)

・ソースファイルBで同様の型、識別子で変数を定義していなければ、その変数のアドレスはnullptrとなる。

 

特定のハードウェア向けのライブラリを実装する際に、出来るだけ手続きを減らしつつリソースの容量、挙動などを変更する場合等に用いられれば便利だろう。

 

さて、肝心の望む構文だが、C++11より加わったattributeの仲間として加えればいいだろう。[ [attribute-name] ]という構文となる。ここにweakと書く。

 

int foo [ [weak] ];        // weak変数foo.
int bar, baz [ [weak] ];   // weak変数はbazのみ.
void func() [ [weak] ] {}  // weak関数func.

 

個人的には変数一つ一つに付ける方が好みである。名前はまだないry

Visual Studio 2013 Communityをインストールすることにした

異文化?コミュニケーションっちゅうんかいの?djannです。

 

現在私のマシンには、学生の時に頂いたVisual Studio 2010 ProfessionalとVisual Studio 2012 Professionalが入っており、主にVisual Studio 2010 Professionalを利用していた。

 

のだが、いい加減普通にコードを書いていると発生してしまう、インテリセンスによるエラー表示(C++11/14の機能)が煩わしくなってきたので、最新のものを入れたくなった(なお、実際のコンパイルにはClang for Windowsを用いているので、コンパイルエラーにはならない)。

ということで、Visual Studio 2013 Communityのインストールを行うことにした。というかいつの間にか新しいVisual Studioが出ていたんだな。いやむしろもうVisual Studio 2015が出ようとしているので、私は遅れに遅れていたらしい。

 

まあ特に無意味に変更箇所をひたすらに眺めていたおかげで、いまだにisoのDLが終了していないので、インストールの模様は記述できないのだが。

もし何か面白いことが起きればここに書き起こそうと思う。何も書かれなければ、何事もなくインストールが終了してしまったのだと思って貰って構わない。

 

 

追記

どうやらうまくいかなかったようだ。早速以下のエラーダイアログが発生してしまった。

f:id:djann:20150304011144j:plain

とりあえずリブートしろとのこと。果たして解消されるのか否か・・・。

 

そういえば一切関係ないが、新しく買ったPCの付属のキーボードが打ちづらくて仕方がない。Tは入力すれば沈んだままになるし、沈まないにしても抵抗がなさすぎだし、ほかのキーは一々抵抗がデカ過ぎる。

見た目と打ち心地以外の機能は気に入っているのがまた残念なところだ。さて、キーボードはどこに廃棄に出せばいいのだろうか?

DirectX 11 part.1

ダイレクトアタック!ライフポイントは0!!djannです。

 

さて、今日から再び・・・数年越しにDirectX 11の学習を再開しようと思う。思い立ったが吉日ということで、早速まずは初期化から書いていく。

なお、ウィンドウの作成に関しては言うまでもないことなので、ここでは記述しないこととする。

 

DirectX 11だが、大抵のグラフィックスAPIと同様に、基本的には以下の流れが存在する。

1. 初期化

2. リソースの作成

3. 状態設定

4. 描画コマンド発行

5. 終了処理

1の後からは2, 3, 4を逐次繰り返しながら描画を行い、最後に5で締めることとなる。

1では描画先のフォーマットや反映のさせ方、反映させる範囲や頻度などを設定する。なお、ここでいくつかのリソースの作成を行うAPIもある。DirectX 11では描画先(レンダーターゲット)のリソースを作成する必要がある(DirectX 9では作成しない)。

 

 

さて、初期化に入っていくわけだが、まずはDevice(デバイス)とContext(コンテキスト)とSwapChain(スワップチェイン)を作成しよう。

DeviceはDirectX 11中で使用出来るリソースを作成するのに用いる。各デバイス(GPU)でのリソースの内容の違いを吸収する部分である。

Contextは様々な状態を維持する。分かりやすく言えばコマンドバッファのようなものだ。GPUへの命令(コマンド)を溜め込み、描画命令でGPUへコマンドを送信し、稼働させる。

SwapChainはGPUで描画した画像を実際の画面に反映させる機能、情報だ。GPUが描画する先は(大抵)今見ている画面そのものではなく、内部的に持っている仮想画面、裏画面、オフスクリーンに行われる。それをどのタイミングで、どのように実際の画面に反映させるかを担当している。

 

上記Device, Context, SwapChainは同時に一気に作れる関数(D3D11CreateDeviceAndSwapChain)が用意されているので、大抵はそれを用いることになるだろう。

 

さて、では実際にD3D11CreateDeviceAndSwapChainを呼び出し、DeviceやContext、SwapChainを作成するコードだが、内容としては以下の説明のようなものとなる。なお、細かすぎるメンバや特に指定する必要がないであろうと思うものは省いてある。

1. 描画先の縦横サイズ、フォーマット(形式)を設定

2. 描画先の更新頻度(60fpsだったり30fpsだったりというあれ)を分数形式で設定

3. 他、描画先の使用方法(まあここではDXGI_USAGE_RENDER_TARGET_OUTPUTだろう)、描画先ウィンドウのハンドル、ウィンドウモードか否か、MSAAのクオリティやカウント等を設定

4. ここまでがSwapChainのパラメータ設定。次にDevice、Contextの能力レベルを設定する。D3D_FEATURE_LEVEL_11_0でDirectX 11の基準を完全に満たしたGPU上で動作するレベルとなる。もし足りない場合は10_1 -> 10_0 -> 9_3 -> 9_2 -> 9_1と、徐々に下げていく等を行い、成功することを祈る。

 

さて、ここまでを以下に、実際のコードとして書いてみた。筆者の環境では当然ながらD3D_FEATURE_LEVEL_11_0で動いた。

 

 

#include <d3d11.h>    // DirectX 11使用の為のヘッダ.
#include <iostream>

#pragma comment( lib, "d3d11.lib")    // DirectX 11使用のためのライブラリリンク.

// 以下しばらくは単なるウィンドウ作成なので無視 -----------------------------------
LRESULT __stdcall proc( HWND hwnd, UINT msg, WPARAM wp, LPARAM lp )
{
	if ( msg == WM_DESTROY ){
		PostQuitMessage( 0 );
		return 0;
	}
	return DefWindowProcA( hwnd, msg, wp, lp );
}
HWND create_window(){
	WNDCLASSEXA wc = {};
	wc.cbSize = sizeof wc;
	wc.hInstance = GetModuleHandleA( nullptr );
	wc.lpszClassName = "test window class";
	wc.lpfnWndProc = proc;
	if ( !RegisterClassExA( &wc ) ){ return nullptr; }
	RECT r = { 0, 0, 1280, 720 };
	AdjustWindowRect( &r, WS_OVERLAPPEDWINDOW, false );

	return CreateWindowExA( 0,
		wc.lpszClassName, wc.lpszClassName,
		WS_OVERLAPPEDWINDOW | WS_VISIBLE,
		CW_USEDEFAULT, CW_USEDEFAULT,
		r.right-r.left, r.bottom-r.top,
		nullptr, nullptr,
		wc.hInstance,
		nullptr
	);
}
// ここまで適当なウィンドウ作成。DirectX 11とは無関係 -----------------------------


int main()
{
	HWND hwnd = create_window();
	// SwapChainの情報指定.
	DXGI_SWAP_CHAIN_DESC desc = {};
	desc.BufferCount = 1;	// オフスクリーンはいくつだ?.
	// ここから1.
	desc.BufferDesc.Width = 1280;
	desc.BufferDesc.Height = 720;	// ここまで縦横解像度.
	desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;	// 描画先の形式.
	// ここから2 - Denomintorが分母.
	desc.BufferDesc.RefreshRate.Numerator = 60;
	desc.BufferDesc.RefreshRate.Denominator = 1;
	// ここから3.
	desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	desc.SampleDesc.Count = 1;
	desc.SampleDesc.Quality = 0;	// ここまでMSAAの設定.
	desc.Windowed = true;			// ウィンドウモードか?.
	desc.OutputWindow = hwnd;		// ウィンドウハンドル.

	// ここからは、特に指定しなくてもいい(0初期化状態のままでいい)と思われるもの.
	desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;	// ストアアプリではDISCARDじゃなくSEQUENTIAL限定か?.
	desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;	// フルスクリーン、ウィンドウを切り替えた際に自動変更されるようになる・・・いらないか.
	// 他、desc.Flagsでは自動回転のオフやGDIの使用を可能にするなどの指定がある.


	// ここから4.
	// とりあえずレベル高い順に記述しておく.
	D3D_FEATURE_LEVEL features[] = {
		D3D_FEATURE_LEVEL_11_0,
		D3D_FEATURE_LEVEL_10_1,
		D3D_FEATURE_LEVEL_10_0,
		D3D_FEATURE_LEVEL_9_3,
		D3D_FEATURE_LEVEL_9_2,
		D3D_FEATURE_LEVEL_9_1,
	};
	// お待ちかねのCreateDeviceAndSwapChainだ。引数の隣に簡単な説明を書いていく.
	// まずは入れ物としてDeviceとなるID3D11DeviceとContextとなるID3D11DeviceContext
	// それとSwapChainとなるIDXGISwapChainのポインタ変数をそれぞれ用意する.
	ID3D11Device *device;
	ID3D11DeviceContext *context;
	IDXGISwapChain *swap_chain;
	D3D_FEATURE_LEVEL level;
	// 戻り値はHRESULTで戻ってくるので、FAILEDマクロに食わせれば成否が分かる・・・マクロか.
	if ( FAILED(
		D3D11CreateDeviceAndSwapChain(
			nullptr,	// どのビデオアダプタを使用するか?既定ならばnullptrで、IDXGIAdapterのアドレスを渡す.
			D3D_DRIVER_TYPE_HARDWARE,	// ドライバのタイプを渡す。これ以外は基本的にソフトウェア実装で、どうしてもという時やデバグ用に用いるべし.
			nullptr,	// 上記をD3D_DRIVER_TYPE_SOFTWAREに設定した際に、その処理を行うDLLのハンドルを渡す。それ以外を指定している際には必ずnullptrを渡す.
			0,			// 何らかのフラグを指定する。詳しくはD3D11_CREATE_DEVICE列挙型で検索検索ぅ.
			nullptr,	// 実はここでD3D_FEATURE_LEVEL列挙型の配列を与える。nullptrにすることで上記featureと同等の内容の配列が使用される.
			0,			// 上記引数で、自分で定義した配列を与えていた場合、その配列の要素数をここに記述する.
			D3D11_SDK_VERSION,	// SDKのバージョン。必ずこの値.
			&desc,		// DXGI_SWAP_CHAIN_DESC構造体のアドレスを設定する。ここで設定した構造愛に設定されているパラメータでSwapChainが作成される.
			&swap_chain,	// 作成が成功した場合に、そのSwapChainのアドレスを格納するポインタ変数へのアドレス。ここで指定したポインタ変数経由でSwapChainを操作する.
			&device,	// 上記とほぼ同様で、こちらにはDeviceのポインタ変数のアドレスを設定する.
			&level,		// 実際に作成に成功したD3D_FEATURE_LEVELを格納するためのD3D_FEATURE_LEVEL列挙型変数のアドレスを設定する.
			&context	// SwapChainやDeviceと同様に、こちらにはContextのポインタ変数のアドレスを設定する.
		)
	) ){
		// 失敗した場合・・・どうしようもない。DirectX 11が使えないかDXGI_SWAP_CHAIN_DESCに不適切な値が入っている・・・.
		std::cout << "failed to initialize the directx 11." << std::endl;
		std::cin.get();
		return -1;
	}
	// 成功したんでもう試行する必要はない.
	std::cout << "Success level : " << std::hex << level << std::endl;

	// さて、各種破棄だ.
	swap_chain->Release();
	context->Release();
	device->Release();

	std::cin.get();

	UnregisterClass( nullptr, GetModuleHandleA( nullptr ) );
	// main関数には暗黙のreturn 0;が存在する.
}

本日はひとまずここまでにしておこう。以降、今更なので需要はなさそうだが、気が向いたと時に徐々に備忘録として書いていく予定だ。

 

これからの身の振り方

右に〜左に〜。いいよいいよ〜。djannです。

 

早速だが、現在私は会社員だ。世の中のほとんどの人間が何らかの会社に務めている昨今、別段珍しいことではない。そしてついでに、その会社へ不満を持つことも当然のようにあることだろう。

 

不満が溜まりに溜まった人間がどうするかというと、転職や起業。または単純に退職だけして、その後のことは一旦何も考えずにいることもあるだろう。

私はまだその域に達していない、達さないと思っていたのだが、もしかしたらそうも行かなくなってしまうかもしれないとここ最近思っている。

 

何が不満かというと実はそんなに無いのだが(偽の裁量労働制だけは比較的どうにかして欲しいとは思うが)、周りの向上心の薄さと謎の自信である。いやもちろん

・毎日遅くまで残って勉強会

・常に自身の反省点を文面化し提出

なんて私もしたくないし、そんなことが強制されていたらいち早くニート生活まっしぐらだとは思うが・・・。

 

ではなんだというと、分からないことがあった場合の対応だ。これが向上心になる。

分からない構文があった -> ではそれを書いた人にもう書かせないようにしよう。

分からない構文があった -> ではそれを書いた人にその構文の意味とメリット/デメリットを聞こう。

向上心という意味ではどちらの方がいいのか、またこの先どちらの方が自身の、ひいては会社のメリットとなるだろうか。

 

ちなみに謎の自信とは

コンパイラや言語の機能は信用できない!だから自分で書く!

という行動原理のことである。

例えば構造体、クラスのメンバの0初期化は当然以下のように行うだろう。

 

struct Foo {
    // さまざまな型のメンバ
};

int main()
{
    Foo f = {};
}

 

それをstd::memset()を用いデータメンバ、仮想関数テーブルを破壊したり、特殊メンバ関数をわざわざ用いずに初期化漏れによるミスをわざわざ一生懸命作りこんでいく。

 

 

そういう状態が、あまり個人的には好ましくなく、身の振り方を考えていくべきかと思い始めている次第である。

まあそうは言っても、どうせ私は色々なことを面倒臭がるタイプなので、結果ズルズルと妥協の道に進んでいく羽目になるのだろうが。

 

私のコードがC++じゃなくなったのを見たら「ああ、この人は堕ちたんだな。」と思ってもらって構わないと思われる。