next up previous
Next: クラスの利用 Up: Cによるオブジェクト指向の記述 Previous: Cによるオブジェクト指向の記述

Subsections

クラスの定義

クラスの定義は、C言語における構造体を利用する。 継承は、構造体の拡張を繰り返すことで実現する。バーチャル関数は、 関数テーブルを記述する構造体を用意することで実現する。 また、1クラスを1ファイルに記述することにより、公開メンバと 非公開メンバの区別をファイルスコープを利用することで実現することが可能で ある。

クラスの記述は1クラスを1ファイル対(ヘッダファイル(*.h)および Cソースファイル(*.c))で記述する。各々に記述すべき内容は次のとおりである。

へッダファイル
    
Cソースファイル
    
なおメンバ関数定義を全く持たない純粋抽象クラスの場合、Cソースファイルは 作成する必要はない。

基本的な概念として、クラスはクラス自身を記述する構造体と関数テーブルを 記述する構造体のペアで構成することに注意する(図2.1)。 なおクラス名はMalibで始まるものとし、その命名規則は、 表2.1に従うものとする(全てのルートクラスとなる オブジェクトクラスの例)。


 
 
Table 2.1: クラス名の命名規則 (Object の例)
クラス名 MalibObject
関数テーブル名 MalibObjectClass


  
Figure 2.1: MalibObject MalibObjectClass
\includegraphics[scale=0.9]{images/malibobject.eps}

クラスの継承

例として、画像フレームを一枚以上保持するホルダクラス(MalibHolder) の実装を挙げる。

MalibHolderMalibObject の直下のサブクラスなので、 MalibObject を直接拡張するように構造体を定義する。また、同時に バーチャル関数テーブル MalibHolderClass 構造体も、 MalibObjectClass 構造体を直接拡張するように定義する (図2.2)。


  
Figure 2.2: クラスの継承
\includegraphics[scale=0.8]{images/inheritancedefinition.eps}

ヘッダファイルにおける定義の実際は、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;

メンバ関数定義

クラスのメンバ関数は、対応するCソースファイルにその内容を記述する。 C言語のファイルスコープを利用することによって、公開メンバ関数 (public menber)と非公開メンバ関数(private member)を区別することが できる。

公開メンバ関数のプロトタイプ宣言は、ヘッダファイルに記述する。 その関数の利用者は、必要なヘッダファイルをインクルードすることで その関数を利用することが可能になる。なお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 の バーチャル関数として定義されるデストラクタは、次のように起動することが できる。
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 が予約されているため、エントリ名称として 利用しないことが望ましいという制約があるためである。

バーチャル関数テーブルの実体

バーチャル関数テーブルの実体は、各クラスのCソースファイル中で静的変数とし て定義される。バーチャル関数テーブルの内容は不変であり、そのインスタンスは ひとつ生成されれば十分であるためである。

バーチャル関数テーブルでは、それらの関数の実体を表す関数名シンボルを 並べて記述する。次の例は、リングバッファクラス(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_deleteMalibObjectClass で定義 される destructor のエントリに対応する。また 次の malib_ringbuf_increment_frameMalibHolderClass で定義されるincrement_frameに対応し、 残りの関数が MalibBufferClass で定義される各関数のエントリに 対応している。次に示す定義は、それぞれのバーチャル関数テーブルの定義 を便宜上入れ子にして表現したものである。先に示した実体の定義と比較して みてほしい(MalibRingBuf では MalibBuffer で用意するバーチャル 関数以外に新たなバーチャル関数を追加していないので、MalibRingBufClassMalibBufferClass を単にラップするだけの ものとなっていることに注意)。

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

各オブジェクトへのバーチャル関数テーブルの設定

ところでC言語はオブジェクト指向言語ではないので、バーチャル関数テーブルを 各オブジェクトに自動的に設定してくれることはない。 したがって、各オブジェクトのコンストラクタで忘れずに バーチャル関数テーブルへのポインタをセットし、 バーチャル関数テーブルへのリンクを張っておく必要がある。

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;
定義を見れば一目瞭然のとおり、このマクロは 第一引数で与えるオブジェクトに第二引数で与える関数テーブルをセットする ものである。


next up previous
Next: クラスの利用 Up: Cによるオブジェクト指向の記述 Previous: Cによるオブジェクト指向の記述
Jun IIO
2001-06-14