最近Civ4ばっかりで飽きてきたので、気分転換にC++をしてみる。
んで、せっかくC++なのだから、とゲーム製作をしてみることに。
僕は現在 CygWin とテキストエディタを使ってコーディングしているので、 DirectX を使用するだけでも一悶着あったりしたのだが、なんとか無事に導入は成功。
どうせなので、後々に銀天ライブラリの一部門にゲームライブラリを加えられるようクラス設計なんかを凝りつつ、まったりと製作中であります。
さて今回は、そんなゲーム製作の途中で、たまたま完成した地味に便利なクラスを紹介しようかと。
さて、そのクラスがどんなものか、用法を適当にさらしてみる:
// クラスの概要は、コンストラクタとデストラクタのみの単純なもの // 実装は後で紹介しますよ class value_saver { public: // コンストラクタで任意の変数の参照を受け取る template< typename T > value_saver( T& t ); ~value_saver(); }; #include <iostream> using namespace std; int main() { int a = 5; // とりあえず a を出力 cout << a << endl; { // こうやって適当な変数を value_saver に渡しておくと value_saver saver( a ); a = 10; // 変数の値を変えても cout << a << endl; // スコープを抜けた時点で、元の値に戻してくれる } // 現在の a の値 cout << a << endl; }
結果:
5 10 5
こんな感じに、変数の値を保存するのに使うオブジェクト。boost::ios_state ライブラリの拡張版とも言う。
受け取る変数はテンプレート指定されてるので、コピーコンストラクタと代入演算さえ定義してあれば、基本的に何でもOK。ま、流石に auto_ptr のような変則オブジェクトは無理だけど。
ただし、型によらない分だけ無駄な処理が増えてるし、型が指定されてないと不安だという人も多そうなので、そういう人(実は筆者も^^;)のため、テンプレート版の typed_saver
int a; typed_saver<int> saver( a );
これでよい。
これら value_saver, typed_saver の用途としては、たとえばある関数の実行中だけ true になるようなフラグを作りたい場合、
static bool doing_hoge = false; // 適当な関数 void hoge() { value_saver saver(doing_hoge); doing_hoge = true; // 何らかの処理。途中で return したり throw したりしても // 関数脱出時に確実にリセットされる } /* void hoge() { doing_hoge = true; // 何らかの処理 doing_hoge = false; // この場合、return や throw により途中で関数を抜けたとき困る } */
こんな感じにすれば、hogeから抜け出すとき doing_hoge が確実に false になることを保障できて便利。
return はともかく、hoge 中で呼び出した関数が例外を全く投げない保障を得ることは難しいので、地味に役に立つのではないかと。
さて実装は、まず後から説明した typed_saver
#include <boost/noncopyable.hpp> template< typename T > class typed_saver : boost::noncopyable { public: typed_saver( T& t ) : target_(t), saver_(t) {} ~typed_saver() { target_ = saver_; } private: T& target_; T saver_; }; // class typed_saver<T>
このように、まさにコンストラクタで対象の変数と現在の値を覚えさせて、デストラクタで戻すだけ。
気をつけるのは一点のみ、コンパイラによるコピーコンストラクタおよび代入演算子が作成されると少々面倒なことになりそうなので、boost::noncopyable でコピーは禁止する、ということだ。正直どのような弊害が生まれるかは分からないが、どうせコピーして使うもんでもないし、そういうものは禁止するのが無難。
さて、では全ての変数に対して使えるように拡張してみる。
このような「全ての変数を格納する」オブジェクトは、既に boost::any クラスがあるので、その実装を真似てみる。
すると、このようになった:
class value_saver : boost::noncopyable { public: template< typename T > value_saver( T& t ) : p_( new holder_<T>(t) ){} // コンストラクタで、型に合った実装のクラスを作り ~value_saver() { delete p_; // デストラクタで削除 } private: // 実装の基本クラス。中身は空 class basic_holder_ : boost::noncopyable { public: virtual ~basic_holder_(){} // デストラクタのみ virtual 指定 }; basic_holder_* p_; // 型ごとの実装。仮想関数つきのクラスから派生する以外は typed_saver<T> と全く同じ template< typename T > class holder_ : public basic_holder_ { public: holder_( T& t ) : target_(t), saver_(t) {} ~holder_() { target_ = saver_; } private: T& target_; T saver_; }; // class holder_<T> }; // class value_saver
ざっと動作を見てみる。まず適当な変数とともにコンストラクタが呼ばれると
int a;
value_saver saver( a );
この時点で渡された変数の型は分かるので、その型情報を頼りに、保存対象の変数と保存した値を保持する holder_ クラスが new 演算子によって構築される。そして、生成された holder_ のアドレスが p_ 変数に代入される。p_ の型は、全ての holder_ の基本クラス basic_holder_ へのポインタなので、どのような holder_ が生成されたとしても問題なく格納できる。
そしてデストラクタでは、今やどんな型の変数が保存されているのかは分からないが、basic_holder_のデストラクタが virtual 指定されているおかげで、確実にコンストラクタで生成した holder_ のデストラクタを呼び出せる。holder_ のデストラクタでは正しい型が分かっているので、問題なく target_ の参照先を保存した値に戻せる、そういう仕組み。
なお蛇足だが、このように C++ において型によらない処理を行う場合、往々にして普段よりコストがかかる。
たとえば今回なら、本来は必要のないポインタと仮想関数呼び出し、new および delete の手間が余計にかかる。
これらは些細な差なので通常は気にすることもないのだが、余計にコストがかかっていることに変わりはないので、実行時の効率を気にする場合は自分の手で型指定をした方がいいだろう。
ま、ソースコードを簡潔に書ける利点の方が、往々にして実行時コストより遥かに大きいんだけどね^^;