インターフェイス・クラスは、一般にライブラリ・モジュールに 作成します。
関数1つにつき、ヘッダファイルに3個所(スキーマ構造体、プロトタイプ宣言、マクロ)と、 ソースファイルに1個所、記述する個所があります。
inf.h
/*********************************************************************** * <<< [Inf] サンプル・インターフェイス >>> ************************************************************************/ INF_TYPEDEF(_Inf, Inf); /* 関数プロトタイプ宣言 */ int Inf_method1( Inf ); void Inf_method2( Inf, int, char* ); void Inf_method3( Inf, int ); /* スキーマ型定義(関数プロトタイプ宣言と同じ構成を記述する) */ struct _Inf_Sc { int (*method1)( void* ); void (*method2)( void*, int, char* ); void (*method3)( void*, int ); }; /*********************************************************************** * <<< Inf インターフェイス・マクロ定義 >>> ************************************************************************/ #ifdef NDEBUG #define Inf_method1(t) INF_MACRO_0( Inf, method1, int, Inf_method1, Inf,t ) #define Inf_method2(t,a,b) INF_MACRO_2( Inf, method2, void, Inf_method2, Inf, int, char*,t,a,b ) #define Inf_method3(t,a) INF_MACRO_1( Inf, method3, void, Inf_method3, Inf, int,t,a ) #endif |
inf.c
/*********************************************************************** * <<< Inf インターフェイス・関数定義 >>> ************************************************************************/ #ifndef NDEBUG INF_FUNC_R0( Inf,method1, int, Inf_method1, Inf, t ) INF_FUNC_2( Inf,method2, void, Inf_method2, Inf,int,char*, t,a,b ) INF_FUNC_1( Inf,method3, void, Inf_method3, Inf,int, t,a ) #endif |
引数の数、返り値の有無、パラメタライズド・クラスの有無、 によって、INF マクロの末尾に異なる略語がつきます。
略語 | 内容 |
---|---|
数字 | オブジェクト(第1引数)を除いた引数の数 |
R | 返り値がある |
S | 第1引数がスキーマ(Xxx_Sc) |
P | 第1引数がパラメタライズド・クラス |
INF_TYPEDEF マクロの内部で以下のように型宣言しています。
#define INF_TYPEDEF( _Inf, Inf ) \ typedef struct _Inf##_Sc Inf##_Sc; \ typedef struct _Inf { \ Inf##_Sc* sc; /* スキーマのアドレス */ \ void* obj; /* オブジェクトのアドレス */ \ } Inf \ typedef struct _Inf##_Pac { \ Inf##_Sc* sc; /* スキーマのアドレス */ \ void* param; /* サブクラス・パラメータのアドレス */ \ } Inf \ |
インプリメント・クラスは、一般にアプリケーション・モジュールに
作成します。
インプリメント・クラスを作成する方向と、既にあるクラスを
インターフェイスに対応する方向のどちらでも構いません。
インプリメント・クラスを作成することは、インターフェイス・クラス とほぼ同じ構成をしたコールバック関数を作成することです。 オブジェクト指向言語では、継承を使ってサブクラスを作成することに 相当します。
/*********************************************************************** * <<< [Sub] インプリメント・クラス >>> ************************************************************************/ typedef struct _Sub { int dummy; /* 既存のクラスにインターフェイスを付ける方向でも構いません */ } Sub; // 通常の関数プロトタイプ宣言 int Sub_method1( Sub*, int, char* ); void Sub_method2( Sub*, int ); void Sub_method3( Sub* ); // インプリメント・クラスとインターフェイスの変換関数のプロトタイプ宣言 Inf_Sc* Sub_getSc_Inf_Sc(void); Inf Sub_inf_Inf( Sub* ); Sub* Sub_by_Inf( Inf ); |
インプリメント・クラスを作成するには、インターフェイス・クラスを参考に、 作成する関数のインターフェイス(名前や引数や返り値など、プロトタイプ宣言に相当) を調べる必要があります。
スキーマ構造体に含まれる関数ポインタの記述から、作成する関数の 引数と返り値がわかります。ということは、method1 関数ポインタが 指し示す関数は、int func( void*, int, char* ); になると 推測されますが、第1引数は、オブジェクト自身を示す変数なので、 インプリメント・クラスの構造体型へのポインタにします。 つまり、インプリメント・クラスの構造体名を Sub とすると、 int func( Sub*, int char* ); となります。 これは、void* 型から Sub* 型に暗黙的にキャストしていることに なりますが、支障はありません。 関数名は、自由につけることが可能ですが、 オブジェクト記述法 COOL に従うことで可読性を上がります。 COOL によると、関数名は、クラス名+操作名です。
インプリメント・クラス Sub の構造体には、インプリメント・クラスの属性を記述します。
もし、属性が1つも無いときは、ダミーを記述してください。
後半の Sub_getSc_Inf_Sc, Sub_inf_Inf, Sub_by_Inf 関数は、
後で説明する INF_SUB で始まるマクロの内部で作られる関数です。
プロトタイプ宣言は作られないので、ここで記述しておきます。
Sub と Inf は、クラス名に応じて改名する必要があります。
Sub_method1 〜 Sub_method3 関数の内容を作成することで、 インプリメント・クラスを作成することができますが、 作成しただけではインターフェイス・クラスと リンクしていません。インターフェイス・クラスとリンクするときは、 本モジュールの INF_SUB で始まるマクロを使用します。
/*********************************************************************** * <<< インプリメント・クラス Sub とインターフェイス Inf の変換関数の定義 >>> * <<< [Sub_sc_Inf, Sub_getSc_Inf_Sc, Sub_inf_Inf, Sub_by_Inf] >>> *【補足】 *・以下の関数を定義しています。 * Inf_Sc* Sub_getSc_Inf_Sc(void); * Inf Sub_inf_Inf( Sub* ); * Sub* Sub_by_Inf( Inf ); ************************************************************************/ INF_SUB_FUNC_START( Inf, Sub ) INF_SUB_FUNC_R2( Inf,Sub,method1, int, Sub_method1, Sub*,int,char*, t,a,b ) INF_SUB_FUNC_1( Inf,Sub,method2, void, Sub_method2, Sub*,int, t,a ) INF_SUB_FUNC_0( Inf,Sub,method3, void, Sub_method3, Sub*, t ) INF_SUB_FUNC_END( Inf, Sub ) |
INF_SUB_FUNC_x マクロの記述は、次の手順で行うと簡単に記述できます。
/*********************************************************************** * <<< [Sub_method1] インプリメント・クラスの関数定義 >>> ************************************************************************/ int Sub_method1( Sub* this ) { (普通に書く) return 0; } |
多態性が必要ない場合、高速化のためにインターフェイス呼び出しの コードを埋め込まなくすることに対応するには、ヘッダファイルの 末尾に次のようなコードを記述します。
sub.h
#ifndef __SUB_H #define __SUB_H (一般的なヘッダファイルの内容) #endif // __SUB_H //以下を記述 #ifdef SUB_EMBED #define Inf Sub* #define Sub_inf_Inf(t) (t) #undef Sub_by_Inf #define Sub_by_Inf(t) (t) #undef Inf_method1 #define Inf_method1(t) Sub_method1(t) #undef Inf_method2 #define Inf_method2(t,a,b) Sub_method2(t,a,b) #undef Inf_method3 #define Inf_method3(t,a) Sub_method3(t,a) #endif |
上記のヘッダファイルをインクルードするときは、次のようにします。
#define SUB_EMBED #include "subtype.h" |
インプリメント・クラスを作成する際に、 デフォルトのインプリメント・クラス(スーパークラス)があるときは、 1からすべて作成しなくても、その差分のみ記述すれば済みます。 ただし、デフォルトのインプリメント・クラス(スーパークラス)を INF_SUB_FUNC_START 〜 INF_SUB_FUNC_END の間に、 INF_SUB_FUNC_OVERRIDE マクロを使用する必要があります。
作成するクラスのプロトタイプ宣言は次のようにします。
/*********************************************************************** * <<< [Sub] サンプル・Inf インプリメント >>> ************************************************************************/ typedef struct _Sub { Super inherit_Super; int x; /* サブクラス特有のメンバ変数 */ } Sub; void Sub_method3( Sub* ); // インプリメント・クラスとインターフェイスの変換関数のプロトタイプ宣言 Inf_Sc* Sub_getSc_Inf_Sc(void); Inf Sub_inf_Inf( Sub* ); Sub* Sub_by_Inf( Inf ); |
構造体の第1メンバ変数には、 デフォルトのインプリメント・クラス(スーパークラス)の構造体を 宣言します。 関数のプロトタイプ宣言は、差分のある関数のみ宣言します。 後半の Sub_getSc_Inf_Sc, Sub_inf_Inf, Sub_by_Inf 関数の プロトタイプ宣言は省略できません。
インターフェイス・クラスとインプリメント・クラスをリンクする INF_SUB で始まるマクロを記述するときは、 INF_SUB_FUNC_OVERRIDE マクロを INF_SUB_FUNC_START マクロの直後に 記述します。引数の Super には、デフォルトのインプリメント・クラス (スーパークラス)の名前を指定します。 そして、スーパークラスの関数を使わない(オーバーライドする)関数に 対してのみ、INF_SUB_FUNC_x マクロを使用します。
/*********************************************************************** * <<< インプリメント・クラス Sub とインターフェイス Inf の変換関数の定義 >>> * <<< [Sub_sc_Inf, Sub_getSc_Inf_Sc, Sub_inf_Inf, Sub_by_Inf] >>> *【補足】 *・以下の関数を定義しています。 * Inf_Sc* Sub_getSc_Inf_Sc(void); * Inf Sub_inf_Inf( Sub* ); * Sub* Sub_by_Inf( Inf ); ************************************************************************/ INF_SUB_FUNC_START( Inf, Sub ) INF_SUB_FUNC_OVERRIDE( Inf, Sub, Super ) INF_SUB_FUNC_0( Inf,Sub,method3, void, Sub_method3, Sub*, t ) INF_SUB_FUNC_END( Inf, Sub ) |
生成関数(new)と破壊関数(delete)は、 スーパークラスの関数が流用できません。 必ずオーバーライドして関数を作成してください。 ただし、作成する関数の内部では、初期化関数(init)と 後始末関数(finish)を呼び出すことができるので、 スーパークラスのメンバ変数の初期化と後始末は、 関数を呼び出すだけで済みます。 後は、サブクラス特有のメンバ変数の初期化と後始末を 記述します。
パラメタライズド・クラスとは、
生成関数(new)によって生成するオブジェクトの
クラスをどのクラスにするかという情報のうち、
特に変数に格納できるものを指します。
パラメタライズド・クラスは、スキーマ構造体へのポインタで
実現することができます。
INF_SUB_FUNC_START 〜 INF_SUB_FUNC_END の間に
INF_SUB_FUNC_SR0 マクロを記述し、
グローバル領域に INF_SUB_PACFUNC0 マクロを記述します。
サブクラスによって生成関数に渡すパラメータが異なる場合、 渡すパラメータ(サブクラス・パラメータ)を格納する 以下のような構造体を作成します。
/************************************************************************** * <<< [Sub_Param] Sub クラスのサブクラス・パラメータ >>> ***************************************************************************/ typedef struct _Sub_Param { int width; int color; } Sub_Param; |
その構造体へのポインタとスキーマ構造体へのポインタを格納する
/*********************************************************************** * <<< [Sub] サンプル・Inf インプリメント >>> ************************************************************************/ typedef struct _Sub { Super inherit_Super; int x; /* サブクラス特有のメンバ変数 */ } Sub; void Sub_method3( Sub* ); // インプリメント・クラスとインターフェイスの変換関数のプロトタイプ宣言 Inf_Sc* Sub_getSc_Inf_Sc(void); Inf Sub_inf_Inf( Sub* ); Sub* Sub_by_Inf( Inf ); Inf_Pac Sub_getPac_Inf( Sub_Param*, int width, int color ); /*********************************************************************** * <<< [main] サンプル・メイン >>> ************************************************************************/ void main() { Sub_Param param; Inf_Pac pac = Sub_getPac_Inf( ¶m, 2, 0xFF ); /* パラメタライズド・クラスの取得 */ Inf x; x = Inf_new_Inf( pac, 1 ); /* パラメタライズド・クラスを使った生成 */ } |
このパラメタライズド・クラス取得関数の第1引数は、 サブクラス・パラメータの構造体の アドレスを指定します。その構造体は、生成関数を 呼び出すときには有効になっている必要があります。 第2引数以降は、サブクラス・パラメータの構造体のメンバ変数と 同じ構成になります。
Inf_Pac 型は、INF_TYPEDEF マクロの内部で宣言されます。
生成関数は、ソースファイルの INF_SUB_FUNC_PRx マクロで
インターフェイスとリンクします。
生成関数の第1引数は、パラメタライズド・クラス変数です。
マクロ名の最後の数値 x は、生成時に指定するパラメータの数です。
Sub_getPac_Inf 関数は、以下に示す INF_SUB_PACFUNCx マクロの内部で
定義されます。マクロ名の最後の数値 x は、パラメタライズド・クラスを
取得するときに指定するパラメータの数です。
第3引数以降の引数は、パラメタライズド・クラス取得関数
(getPac)のプロトタイプ宣言の引数からコピーすると簡単に記述できます。
/*********************************************************************** * <<< インプリメント・クラス Sub とインターフェイス Inf の変換関数の定義 >>> * <<< [Sub_sc_Inf, Sub_getSc_Inf_Sc, Sub_inf_Inf, Sub_by_Inf, Sub_getPac_Inf] >>> *【補足】 *・以下の関数を定義しています。 * Inf_Sc* Sub_getSc_Inf_Sc(void); * Inf Sub_inf_Inf( Sub* ); * Sub* Sub_by_Inf( Inf ); * Inf_Pac Sub_getPac_Inf( Sub_Param*, int width, int color ); ************************************************************************/ INF_SUB_FUNC_START( Inf, Sub ) INF_SUB_FUNC_PR0( Inf,Sub,new_Inf, int, Sub_new_Inf, Sub_Pac, t ) INF_SUB_FUNC_1( Inf,Sub,method2, void, Sub_method2, Sub*,int, t,a ) INF_SUB_FUNC_0( Inf,Sub,method3, void, Sub_method3, Sub*, t ) INF_SUB_FUNC_END( Inf, Sub ) INF_SUB_PACFUNC2( Inf, Sub, Sub_Param*, int, width, int, color ) |
参考:→COOL 7-3章
インターフェイスの関数が1つのときは、メソッド・ポインタを 使うことで、簡単にインターフェイス呼び出しができるようになります。
使い方は次のとおりです。
返り値は、int 型です。必要に応じて、ポインタ型に変えてください。
void main() { Inf_Mp mp; Data data; Inf_Mp_init( &mp, &data, func, 2 ); // func 関数を呼び出すメソッドポインタ // func 関数の this 以外の引数の数は 2 sub( &mp ); } Data* sub( Inf_Mp* mp ) { return (Data*)Inf_Mp_call2( mp, "str", 5 ); // func 関数の呼び出し } int func( Data* this, char* s, int n ) { return (int)this; } |