C++

例外処理について

これから数回、プログラミング言語の機能で、積極的に利用すべきものであるのに、未だに利用していない機能でも語っていこうと思います。第一回は例外処理です。

プログラミング言語の機能として、例外処理というものがあります。オブジェクト指向プログラミングを取り入れたプログラミング言語によく見られる機能であり、エラー処理を簡単に記述したり、エラー処理忘れを防止できたりする機能です。また、例外処理では、エラーの詳しい情報も取得できます。

しかしながら、私は本格的に例外処理機能を使ってプログラムを組んだことがありません。何故かと言えば、単純に例外処理を導入するのが面倒なのもありますし、ライブラリが例外処理に対応してなかったりすると、そこに例外処理を入れてしまうと、何か中途半端となってしまうからです。

Javaのように、例外が積極的に使われているどころか、無視することができない場合は、積極的に例外処理を利用しますが、特別例外処理を必要とせずとも、プログラムすることの出来る場合は、例外処理を使おうとは思いません。むしろ、先に述べたことの繰り返しとなりますが、既存のライブラリなどが、例外処理を使っていない場合、使っているコードと使っていないコードを混在させるのは、何か気持ち悪くてできないのです。

というわけで、特にパフォーマンスが気にならなければ、積極的に例外処理を使ったコードを書くべきだとは思っているのですが、なかなかそのようなスタイルが身につきません。

あと、個人的な話としては、例外処理をやるとtry-catch-finallyでインデントが1つ増えてしまって、コードのインデントがやけに多くなってきてしまうので、そこのところ、どうにかうまい書き方はないのかと思ったりもしています。

イテレート中の要素削除

STLの話です。リストの要素をイテレートしながら要素を削除したいときがありますが、そういう場合はこういう風に書きたいわけです。

for ( list<GEAnimation *>::iterator it = m_Animations.begin(); it != m_Animations.end(); it++ ) {
	if ( (*it)->step( this ) ) {
		delete *it;
		m_Animations.remove( *it );
	}
}

gccでは動くのですが、MSのSTLでは動いてくれないので、こういう風に書く必要があります。

list<GEAnimation *>::iterator it = m_Animations.begin();
while ( it !=  m_Animations.end() ) {
	if ( (*it)->step( this ) ) {
		delete *it;
		it = m_Animations.erase( it );
	} else {
		it++;
	}
}

forを使った書き方をよく使うので、そっちで動くように融通がきいてくれればありがたいんですけどね。。。どんな風に書くのがいいんでしょうね。

Visual C++ 2008 で作成したプログラムの配布

てっきり、Visual Studio C++ 2008 で作成したプログラムは、Windowsなら動いてくれるものかと思っていましたが、Visual Studio C++ 2008 がインストールされていないコンピュータで動かすためには、いくつかのdllが必要なこともあるようです。

それらをまとめたパッケージがMicrosoftのダウンロードページから手に入ります。
Microsoft Visual C++ 2008 再頒布可能パッケージ (x86)

自分で試す環境に、常にVisual Studioが入っていたので、まったく気づきませんでした。

C++クラスをLuaから使う

Luaスクリプト言語はC言語と親和性が高く、C言語のプログラムにLuaスクリプトを組み込むことによってLuaの関数が呼べたり、逆にLuaからC関数を呼び出すことも可能となっています。前回は、Luaだけでオブジェクト指向を実現するコードを書いてみましたが、今回は、LuaにC++のクラスを公開するコードを書いてみました。

C++のコード:

#include <stdio.h>
#include "lua.hpp"
#include <iostream>

#include <string>
using namespace std;

class CObject {
	string m_Name;
public:
	CObject(string name);
	~CObject();

	string description();
	string getName();
};

CObject::CObject(string name)
{
	m_Name = name;
}

CObject::~CObject()
{
	cout < < m_Name << " is released." << endl;
}

string CObject::description()
{
	return "CObject: " + m_Name;
}

string CObject::getName()
{
	return m_Name;
}

int tolua_CObject_alloc(lua_State *L)
{

	lua_newuserdata(L, sizeof(CObject *)); //2
	lua_newtable(L); //3
	lua_getfield(L, 1, "className"); //4
	const char *name = luaL_checkstring(L, 4);
	lua_getglobal(L, name); //5 -> 4
	lua_remove(L, 4);
	lua_getfield(L, 4, "description");  //5
	lua_setfield(L, 3, "__tostring");
	lua_setfield(L, 3, "__index");
	lua_setmetatable(L, 2);
	return 1;
}

int tolua_CObject_release(lua_State *L)
{
	CObject *p = *(CObject **)lua_touserdata(L, 1);
	delete p;
	return 0;
}

int tolua_CObject_init(lua_State *L)
{
	CObject **p = (CObject **)lua_touserdata(L, 1);
	const char *name = luaL_checkstring(L, 2);
	*p = new CObject(name);
	lua_settop(L, 1);
	return 1;
}

int tolua_CObject_description(lua_State *L)
{
	CObject *p = *(CObject **)lua_touserdata(L, 1);
	lua_pushstring(L, p->description().c_str());
	return 1;
}

int tolua_CObject_getName(lua_State *L)
{
	CObject *p = *(CObject **)lua_touserdata(L, 1);
	lua_pushstring(L, p->getName().c_str());
	return 1;
}

int tolua_CObject(lua_State *L)
{
	lua_newtable(L);
	lua_pushstring(L, "CObject");
	lua_setfield(L, -2, "className");
	lua_pushcfunction(L, tolua_CObject_alloc);
	lua_setfield(L, -2, "alloc");
	lua_pushcfunction(L, tolua_CObject_release);
	lua_setfield(L, -2, "release");
	lua_pushcfunction(L, tolua_CObject_init);
	lua_setfield(L, -2, "init");
	lua_pushcfunction(L, tolua_CObject_description);
	lua_setfield(L, -2, "description");
	lua_pushcfunction(L, tolua_CObject_getName);
	lua_setfield(L, -2, "getName");
	return 1;
}

int main (int argc, const char * argv[]) {
    lua_State *L = lua_open();
	luaL_openlibs(L);

	lua_register(L, "CClass_CObject", tolua_CObject);

	while ( getchar() != 'e') {
		if ( luaL_dofile(L, "hello.lua") ) {
			cout < < lua_tostring(L, -1) << endl;
		}
	}

	lua_close(L);
    return 0;
}

hello.luaのコード

print( "Hello Lua!" )

CObject = CClass_CObject()

obj = CObject:alloc():init("anObject")
print( obj )
print( obj:getName() )
obj:release()

やってることは、前回のLua上でオブジェクトシステムを作ったやり方とほぼ同じで、それをC言語からやっています。重要な点と言えば、クラスを公開する場合は、ライトユーザーデータではなく、フルユーザーデータのほうを使う点です。ライトユーザーデータのほうは、単にポインタを数値的にluaとやり取りするだけなので、メタテーブルを設定することができません。しかし、フルユーザーデータのほうでは、メタテーブルを持つことができます。したがって、フルーユーザーデータを作成し、そこの領域にインスタンスのアドレスを入れることによって、Lua側でメソッドを実行した時にthisポインタを得ることが可能になります。

メタテーブルの”__gc”を設定することによって、Lua側でガベージコレクトされた時に、インスタンスをdeleteすることも可能ですが、Luaから見えないところ(C++側など)でインスタンスが保持されている場合も考えられるので、なんらかの規則を導入しない限りお勧めできません。

ちなみに、前回のオブジェクトシステムと組み合わせて、C++側から公開されたクラスを、Lua側で継承させることができそうですが、この方法ではまだ不十分です。フルユーザーデータは、あくまでテーブルではないので、Lua側で勝手にインスタンス変数を追加することができません。まだ試していないのですが、おそらく”__newindex”のメタテーブルを使って、インスタンス変数の追加を実現することも可能でしょう。

マルチプラットフォームでゲームを開発するならSDL

基本的にカテゴリで「未分類」になってしまうようなブログネタは避けているので、最近いろいろありそうでSDLばかり触っていたばかりに、ブログの更新が相変わらず停滞気味です。しかし、さっき本屋で良さそうな本を見つけたので、「Book」のカテゴリとして更新です。

スクリプト言語による効率的ゲーム開発 C/C++へのLua組込み実践 (GAME DEVELOPER)
4797348550
浜中 誠


Amazonで詳しく見る
by G-Tools

ゲーム開発の書籍は色々出ていますが、ゲームのシステム全体のロジックについて考えたい場合は、これが一番良いかもしれません。ゲームプログラミングでの悩みどころと言えば、ゲームの進行をどうやってプログラムしていくか、というところだと思います。このゲームの進行の部分は、上から下へ進む単調なプログラムとして書けないことは、一度ゲームを開発しようと思ったことのある方には理解できるでしょう。そこでスクリプト言語の登場です。この本を読めば、このゲームの進行をどのように解決していくか、その糸口がつかめることでしょう。

また、この本でスクリプト言語の使用を推奨している最大の理由は、動的にゲームの調整が出来る点です。細かいチューニングをして、ビルドを繰り返していれば、大変な時間がかかっています。この本には、スクリプトを動的に読み込むなどして、それらを解決する具体的な方法が提示されています。

この本のサンプルは、意図的に思える程にゲームのほとんどの部分をスクリプト言語に落とし込んでいます。ここまでスクリプトに落とし込む設計しても悪くはないのか、と思いつつ読みました。ただし、もちろんパフォーマンスとのトレードオフなので、そのへんのバランスについても、ある程度この本で書かれています。

一応注意書きとして「ゲーム作りたいけど、全く見当もつきません」といった方が読む本というよりは、「ゲーム全体の設計が今ひとつしっくりと来ない」といった悩みや、本のタイトル通りの要望をお持ちの方に大変お薦めできる本です。

ところで、この本に書かれているサンプルにはSDL+OpenGLが使われています。これらはほとんどのプラットフォームで動作するので、Macでもゲームを動作させたいなら、このSDLを使うのが一番速い解決方法かと思います。ちなみに、今回紹介した本の中では、これらSDL+OpenGLの解説はほとんど無いので、その辺り勘違いしないように注意してください。

ちなみに、私はそれほどゲームに興味はありません。

vector配列(←なぜかコピーされる)

Objective-Cに慣れすぎてしまったせいで、すっかりC++/ActiveBasic風のオブジェクトのインスタンス化についての感覚がどこかへ消えてしまったようです。

NSObject *object = [[NSObject alloc] init];
//何か処理
[object release];

Objective-Cでは、このような方法でしかオブジェクトのインスタンス化を行うことができません。C++のnew/deleteでインスタンスを作る方法と一緒です。

しかしC++などでは、こちらの方法でインスタンス化をすることでしょう。ただ単に、クラス名を型に持ってきて、変数を宣言する方法です。

MyObject object;

それで、このタイプのインスタンスであると、関数に値を渡すときに、引数に新しくオブジェクトが作成されるわけです。作成といってもコピーですが。

例えばこんなクラスを定義して調べてみます。コピーコンストラクタが呼ばれたときに、カウントを1増やすオブジェクトです。

#include <iostream>

class Object {
public:
	int count;

	Object()
	{
		count = 0;
		std::cout << "construct object " << count << std::endl;
	}

	~Object()
	{
		std::cout << "destroy object " << count << std::endl;
	}

	Object(const Object& object)
	{
		count = object.count+1;
		std::cout << "copy construct " << count << std::endl;
	}

	void print()
	{
		std::cout <<"print " << count << std::endl;
	}
};

このクラスを使い、適当に関数を作って実行してみます。

void fx(Object anObject)
{
	anObject.print();
}

int main(int argc, char *argv[])
{
	Object object;
	fx(object);
	return 0;
}

実行結果:

construct object 0
copy construct 1
print 1
destruct object 1
destruct object 0

当然ですね。引数に値コピーするので、コピーコンストラクタが1回呼ばれます。引数を参照型にすれば、コピーされることはありません。

さて、ここまでは前置きです。ここでSTLのlist配列に入れて検証してみましょう。

int main(int argc, char *argv[])
{
	using namespace std;

	Object object;
	list<Object> array;

	for ( int i = 0; i < = 8; i++ ) {
		array.push_back(object);
	}

	for ( list<Object>::iterator it = array.begin(); it != array.end(); ++it ) {
		(*it).print();
	}
	return 0;
}

実行結果:

construct object 0
copy construct 1
copy construct 1
copy construct 1
copy construct 1
copy construct 1
copy construct 1
copy construct 1
copy construct 1
copy construct 1
print 1
print 1
print 1
print 1
print 1
print 1
print 1
print 1
print 1
destruct object 1
destruct object 1
destruct object 1
destruct object 1
destruct object 1
destruct object 1
destruct object 1
destruct object 1
destruct object 1
destruct object 0

これは期待通りの動作です。list配列に入れるときにオブジェクトをコピーするのは納得です。もしここで実態が同じオブジェクトを配列に入れてしまうと、簡単にバグを引き起こしかねません。

ところで、これと同じことを今度はvector配列でやってみます。

int main(int argc, char *argv[])
{
	using namespace std;

	Object object;
	vector<:Object> array;

	for ( int i = 0; i < = 8; i++ ) {
		array.push_back(object);
	}

	for ( vector<Object>::iterator it = array.begin(); it != array.end(); ++it ) {
		(*it).print();
	}

実行結果:

construct object 0
copy construct 1
copy construct 2
copy construct 1
destruct object 1
copy construct 3
copy construct 2
copy construct 1
destruct object 2
destruct object 1
copy construct 1
copy construct 4
copy construct 3
copy construct 2
copy construct 2
copy construct 1
destruct object 3
destruct object 2
destruct object 1
destruct object 1
copy construct 1
copy construct 1
copy construct 1
copy construct 5
copy construct 4
copy construct 3
copy construct 3
copy construct 2
copy construct 2
copy construct 2
copy construct 2
copy construct 1
destruct object 4
destruct object 3
destruct object 2
destruct object 2
destruct object 1
destruct object 1
destruct object 1
destruct object 1
print 5
print 4
print 3
print 3
print 2
print 2
print 2
print 2
print 1
destruct object 5
destruct object 4
destruct object 3
destruct object 3
destruct object 2
destruct object 2
destruct object 2
destruct object 2
destruct object 1
destruct object 0

listとは大きく違い、多くのコピーが発生しています。なぜこのようなコピーが発生するのかといえば、おそらくvector内部で再度メモリ確保を行ってオブジェクトを移動する際、すべてコピーされていると推測することができます。

このような動作をするから何か問題があるというわけではありませんが、vector配列でコピーが起こす意図が分かりません。おそらく、コピーを発生させないようにもvector配列は実装できると思うのですが…

Objective-C++

私がこれまでにメインで使ってきた言語は、ActiveBasicとObjective-Cなわけですが、これら2つはどちらもマイナーな言語です。というわけもあり、最近C++の必要性を感じてきたこともあって、C++をこれから覚えようかと思っています。もちろん、Cを含めた標準ライブラリもほとんど触ったことが無いので、STLくらいまでは使えるようにしたいと思っているところです。

そういわけで、ちょっとC++を触ってみたわけですが、やはりObjective-Cとは正反対の感覚を感じます。C++はテンプレートという文法取り入れ、コンパイル時に全ての型のコードを展開することによって、データ型にとらわれないプログラミングを行うことができるのに対し、Objective-Cは、特にこれといった文法は取り入れず、動的なメソッド解決によってデータ型にとらわれないプログラミングを提供しているからです。どちらが優れいてるというわけではありませんが、C++は頑張っていて、Objective-Cはさぼっている言語のような気分を抱かなくはないです。

ところで、C++とObjective-Cのコードを同時に書くことのできるObjective-C++というものがあります。想像するだけでもかなりカオスな状態になりそうで、一体誰が使うのだろうかと思うわけですが、Web Kitの一部でObjective-C++が使われているらしいです。

参考資料:C++ と Objective-C の併用