クラスの記述は1クラスを1ファイル対(ヘッダファイル(*.h)および Cソースファイル(*.c))で記述する。各々に記述すべき内容は次のとおりである。
基本的な概念として、クラスはクラス自身を記述する構造体と関数テーブルを 記述する構造体のペアで構成することに注意する(図2.1)。 なおクラス名はMalibで始まるものとし、その命名規則は、 表2.1に従うものとする(全てのルートクラスとなる オブジェクトクラスの例)。
MalibHolder は MalibObject の直下のサブクラスなので、 MalibObject を直接拡張するように構造体を定義する。また、同時に バーチャル関数テーブル MalibHolderClass 構造体も、 MalibObjectClass 構造体を直接拡張するように定義する (図2.2)。
ヘッダファイルにおける定義の実際は、MalibHolder の場合は以下のとおりの記述となる。クラス構造体、 バーチャル関数テーブル構造体共に、上位の MalibObject および MalibObjectClass 型の構造体が先頭に配置されていることに注意。
struct _MalibHolder
{
MalibObject super;
guint size;
guint current_idx;
MalibFrame** frames;
MalibSource* source;
};
struct _MalibHolderClass
{
MalibObjectClass super;
void (* increment_frame) (MalibHolder* buf);
};
なお構造体定義における相互参照を可能にするために、
クラス名は typedef を用いて
次のように定義し、構造体名の実際はクラス名の直前に「_」
(アンダースコア)を附加したものを用いて定義する。
typedef struct _MalibHolder MalibHolder; typedef struct _MalibHolderClass MalibHolderClass;
公開メンバ関数のプロトタイプ宣言は、ヘッダファイルに記述する。
その関数の利用者は、必要なヘッダファイルをインクルードすることで
その関数を利用することが可能になる。なおMAlibをC++から利用することを
鑑み、公開メンバ関数のプロトタイプ宣言は、以下の extern "C"
宣言で囲んでおく。
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
... (公開メンバ関数のプロトタイプ宣言)
#ifdef __cplusplus
}
#endif /* __cplusplus */
一方、非公開メンバ関数はプロトタイプ宣言をヘッダファイルに記載せず、 Cソースファイルの先頭で宣言する。また、非公開メンバ関数には static 修飾子をつけた宣言および定義を行なう。 これによりそれらの関数はプライベートな関数としてそのCソースファイルの 中でのみ利用することができ、他からアクセスすることは不可能となる。
なおライブラリの利用者の便宜を考慮し、全てのヘッダファイルをまとめて インクルードするためのヘッダファイル malib.h が用意されている。 malib.h はスクリプト mk_malib_h.pl によって自動的に 作成され、ライブラリの make 時に最新の内容に更新されるように 設定されている。
malib_クラス_関数名
また、原則としてメンバ関数の第一引数にはそのオブジェクトへのポインタを 渡す。例えば、MalibPlainBuf のメンバ関数として void 型で int 型の引数をひとつとる関数 a_method を 追加するのであれば、そのシグネチャは次のようになる。
void malib_plainbuf_a_method (MalibPlainBuf*, int);
ただし、コンストラクタおよびクラスメソッドに相当する関数についてはその限 りではない。
このような状況を回避するために、衝突した関数名のうちマクロ定義ではな
いほうの関数名の最後に「_」(アンダースコア)を附加するというルール
を加える。このような状況の例は、MalibFrame クラスで生じている。
MalibObject* obj = malib_object_new (); ... (* obj->klass->destructor) (obj);
実際には、オブジェクトは MalibObject のサブクラスのインスタンスと して生成されるので、次のように型変換を行なう必要がある。
MalibRingbuf* obj = malib_ringbuf_new (); ... (* ((MalibObject*)obj)->klass->destructor) ((MalibObject*)obj);
また、サブクラスで定義されるバーチャル関数を起動する場合は、 必要に応じてクラス構造体も型変換しなければならない(次の例を参照)。
MalibRingbuf* obj = malib_ringbuf_new ();
...
(* ((MalibHolderClass*)((MalibObject*)obj)->klass)->increment_frame)
((MalibHolder*)obj);
MALIB_クラス_VFUNC_TBL()として定義)、およびそれを利用して
バーチャル関数自体を定義するものとして記述される。MalibSource
クラスの例を次に示す(MALIB_OBJECT_VFUNC_TBL()
は object.h で定義されている)。
#define MALIB_SOURCE_VFUNC_TBL(obj) \
((MalibSourceClass*)MALIB_OBJECT_VFUNC_TBL(obj))
#define malib_source_write_frame_data(src, frame) \
(* (MALIB_SOURCE_VFUNC_TBL(src))->write_frame_data) (src, frame)
慣例として、バーチャル関数テーブルのエントリの名称はバーチャル関数名 からクラス名を切り離した残りの部分、すなわち上記の例で あれば write_frame_data とすると分かりやすい。
例外としてデストラクタのみは、関数テーブルのエントリ名 が destructor であるにも関わらず 関数名を malib_object_delete() としている。これは、C++ の 予約語として delete が予約されているため、エントリ名称として 利用しないことが望ましいという制約があるためである。
バーチャル関数テーブルでは、それらの関数の実体を表す関数名シンボルを 並べて記述する。次の例は、リングバッファクラス(MalibRingBuf)の バーチャル関数テーブルの例である。型変換を伴うので若干複雑に見えるが、 各バーチャル関数に対応する実際の関数名が並べられているだけであり その実体は非常に簡潔に定義される。
static MalibRingBufClass malib_ringbuf_class =
{
(void (*)(MalibObject*)) malib_holder_delete,
(void (*)(MalibHolder*)) malib_ringbuf_increment_frame,
(MalibFrame* (*)(MalibBuffer*, gint)) malib_ringbuf_get_frame,
(MalibFrame* (*)(MalibBuffer*)) malib_ringbuf_get_current_frame,
(MalibFrame* (*)(MalibBuffer*)) malib_ringbuf_get_next_frame,
(MalibFrame* (*)(MalibBuffer*)) malib_ringbuf_get_prev_frame
};
この例では、 一番目の malib_holder_delete が MalibObjectClass で定義 される destructor のエントリに対応する。また 次の malib_ringbuf_increment_frame が MalibHolderClass で定義されるincrement_frameに対応し、 残りの関数が MalibBufferClass で定義される各関数のエントリに 対応している。次に示す定義は、それぞれのバーチャル関数テーブルの定義 を便宜上入れ子にして表現したものである。先に示した実体の定義と比較して みてほしい(MalibRingBuf では MalibBuffer で用意するバーチャル 関数以外に新たなバーチャル関数を追加していないので、MalibRingBufClass が MalibBufferClass を単にラップするだけの ものとなっていることに注意)。
struct _MalibRingBufClass {
struct _MalibBufferClass {
struct _MalibHolderClass {
struct _MalibObjectClass {
void (* destructor) (MalibObject* object);
};
void (* increment_frame) (MalibHolder* buf);
};
MalibFrame* (* get_frame) (MalibBuffer* buffer,
gint offset);
MalibFrame* (* get_current_frame) (MalibBuffer* buffer);
MalibFrame* (* get_next_frame) (MalibBuffer* buffer);
MalibFrame* (* get_prev_frame) (MalibBuffer* buffer);
};
};
またこの例では、デストラクタは上位クラスの提供しているバーチャル関数を そのまま利用し、その他の関数については自身のクラスでオーバライドしている ことが示されている。
なおobject.hで定義されるマクロ MALIB_OBJECT_CHECK_CLASS(obj, name)は、 オブジェクトがどのクラスのインスタンスであるかを判定するマクロ 定義である。 このマクロ定義を有効に機能させるためには、バーチャル関数テーブル自体の命名 規則として、次のようなメンバ関数に類似の命名規則にする必要がある。
malib_クラス_class
object.h では関数ポインタをセットするためのマクロを用意して
いるので、バーチャル関数テーブルを設定する際にはマクロ
MALIB_OBJECT_SET_VFUNC_TBL() を利用するとよい。
#define MALIB_OBJECT_VFUNC_TBL(obj) (((MalibObject*)obj)->klass)
#define MALIB_OBJECT_SET_VFUNC_TBL(obj, tbl) \
MALIB_OBJECT_VFUNC_TBL(obj) = (MalibObjectClass*)tbl;
定義を見れば一目瞭然のとおり、このマクロは
第一引数で与えるオブジェクトに第二引数で与える関数テーブルをセットする
ものである。