AngelScript 3
少し間が空いてしまったのだけれど、AngelScript三回目。
今回も、言語側のことについて。クラスについて書く。
クラス宣言
C++を使ってきた人にとっては、ほとんど何も意識することなくクラスを宣言することができる。
/**/
class Hoge
{
Hoge()
{
Print("Hoge::Hoge()");
}
~Hoge()
{
Print("Hoge::~Hoge()");
}
int value_;
}
コンストラクタ、デストラクタはC++と同じくクラス名となっている。アクセス制御子はない。全てpublicになっている。(後述するように、アクセッサは作ることができるので命名規約など作って保護することは可能。)
初期化
初期化方法には次のようなものがある。
/*デフォルトコンストラクタが呼ばれる*/ Hoge h0; /*明示的にデフォルトコンストラクタを呼び出す場合*/ Hoge h1(); /*コンストラクタもオーバーロード可能*/ Hoge h2(10);
オブジェクトハンドル
AngelScriptは、限りなくシンタックスがC/C++に近いが、ポインタは意図的にサポートしていない。変わりにオブジェクトハンドルをサポートしている。shared_ptrに似ている。
Hoge h0;
/*h1にh0を参照させる。*/
Hoge@ h1 = h0;
/*isで、等価性を判定できる。*/
h2 is h1 ? Print("h2==h1") : Print("h2 != h1");
/*C++の参照と違い、ハンドルだけ宣言する事も可能。*/
Hoge@ h2;
ハンドルは参照先と参照元のライフタイムが異なるときに威力を発揮する。次の例では、参照元がスコープから抜けてしまっているが、スコープ外の参照に参照されているので、依然として参照元が残っている。これは参照する際、そのインスタンスのAddRefが呼ばれることで実現している。そしてこれのメソッドは、C/C++にバインドする際にカスタマイズすることもできる。またハンドルは、C/C++と通信する際もかなり重要な要素になる。この二つについては、多分バインドの回で詳しく。
Hoge@ h0;
{
Hoge h1;
@h0 = @h1;/*ここでHogeの参照がインクリメントされる*/
}
h0;/*これはh1を参照しており、実際にh1はまだ生きている。*/
循環参照はGCが呼ばれるか、asIScriptContextが解放されるまで解決しない。
/**/
class A
{
A( )
{
Print("A::A");
}
~A()
{
Print("A::~A");
}
void SetB( B@ b)
{
@b_ = @b;
}
B@ b_;
}
/**/
class B
{
B()
{
Print("B::B");
}
~B()
{
Print("B::~B");
}
void SetA( A@ a )
{
@a_ = @a;
}
A@ a_;
}
/**/
void test()
{
Print("Start Scope");
{
A@ a = A();
B@ b = B();
a.SetB( b );
b.SetA( a );
/*
残念ながらここで、a,bは解放はされない。
GCを呼ぶか、asIScriptContextを解放しないといけない
*/
}
Print("End Scope");
}
継承
継承ができる。
class Foo : Hoge
{
Foo()
{
Print("Foo::Foo()");
}
~Foo()
{
Print("Foo::~Foo()");
}
void Func()
{
Print("Foo::Func()");
}
}
継承関係にあるものはキャストを行うことができる。キャストに失敗すると例外が呼ばれる。(asEXECUTION_EXCEPTIONが帰る。)
Foo f; Hoge@ h = f; Foo@ f2 = cast<Foo>( h );
また、全てのメソッドはオーバーライド可能。オーバーライドの際、特別何かを宣言する必要もない。
f2.Func();/*Hoge::Func()ではなく、Foo::Func()が呼ばれる*/
インターフェイス
継承ができるのでそれでも十分だが、インターフェイスを明示的に作りたい場合はinterfaceを宣言できる。
interface IHuman
{
void Walk();
}
class Bob : IHuman
{
void Walk()
{
Print("Bob::Walk");
}
}
オペレーター
オペーターオーバーロードは、指定の関数名をオーバーロードすることによって実現する。オーバーロードできる演算子は次のとおり。というかこぴぺ。
○単項演算子
演算子 相当する関数
- opNeg
~ opCom
○比較演算子
演算子 相当する関数
== opEquals
!= opEquals
< opCmp
<= opCmp
> opCmp
>= opCmp
○代入演算子
演算子 相当する関数
= opAssign
+= opAddAssign
-= opSubAssign
*= opMulAssign
/= opDivAssign
%= opModAssign
&= opAndAssign
|= opOrAssign
^= opXorAssign
< <= opShlAssign
>>= opShrAssign
>>>= opUShrAssign
○二項演算子
演算子 相当する関数 二項の順番が逆の時に呼ばれる関数
+ opAdd opAdd_r
- opSub opSub_r
* opMul opMul_r
/ opDiv opDiv_r
% opMod opMod_r
& opAnd opAnd_r
| opOr opOr_r
^ opXor opXor_r
< < opShl opShl_r
>> opShr opShr_r
>>> opUShr opUShr_r
次のように使う。
class Vec3
{
Vec3( float x, float y, float z )
{
x_ = x;
y_ = y;
z_ = z;
}
/*+*/
Vec3@ opAdd(const Vec3 &in rhs)
{
return Vec3( x_ + rhs.x_, y_ + rhs.y_, z_ + rhs.z_ );
}
/* += */
Vec3@ opAddAssign( const Vec3 &in rhs )
{
x_ += rhs.x_;
y_ += rhs.y_;
z_ += rhs.z_;
return this;
}
float x_;
float y_;
float z_;
}
void test()
{
Vec3 v1(0.0f,1.0f,2.0f);
Vec3 v2(1.0f,3.0f,0.0f);
Vec3 v3 = v1 + v2;
}
プロパティアクセッサ
get_***,set_***のような関数は、***に相当するプロパティのgetter,setterになる。
class Human
{
/*名前の設定*/
void set_name( string name )
{
strValue_ = name;
}
/**/
string get_name() const
{
/*デバッグ用にいつだってアリス*/
return "Alice";
//return strValue_;
}
string strValue_;
}
/**/
void test()
{
Human human;
human.name = "Bob";
Print( human.name );
}
ふう。これでクラス周りは一通り回ったと思う。何かあったらクラス関連のことは、このエントリを適宜修正、加筆する。
今のところ、弱点らしい弱点(妙な構文や変な制限)がない気がする。マジで広める価値はあるのかもしれない。
