●多態インターフェイス [Inf]


1. 概要

1-1. インターフェイスとは

1-2. インプリメント・クラスとは

1-3. サブクラスとは


2. インターフェイス・クラスの作成方法

2-1. インターフェイス・クラスの記述

インターフェイス・クラスは、一般にライブラリ・モジュールに 作成します。

関数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
スキーマの関数ポインタの第1引数は、Inf 型でなく void* にしてください。

スキーマ構造体は、上記の struct _Inf_Sc のように 関数ポインタからなる構造体のことです。 このスキーマ構造体がインターフェイス・クラスの実体です。 つまり、インプリメント・クラスは、これらの関数ポインタが 指し示す関数を集めたものになります。

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_FUNC_... の記述は、ヘッダファイルに記述してある INF_MACRO_... の行をコピーペーストして、 #define をカットし、MACRO を FUNC に変えるだけ済みます。

2-2. INF マクロの末尾の略語の意味

引数の数、返り値の有無、パラメタライズド・クラスの有無、 によって、INF マクロの末尾に異なる略語がつきます。

略語内容
数字オブジェクト(第1引数)を除いた引数の数
R返り値がある
S第1引数がスキーマ(Xxx_Sc)
P第1引数がパラメタライズド・クラス

2-3. Inf 型構造体、Inf_Pac 型構造体 の内容

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 \


3. インプリメント・クラスの作成方法

3-1. インプリメント・クラスの記述(1) 〜 プロトタイプ宣言

インプリメント・クラスは、一般にアプリケーション・モジュールに 作成します。
インプリメント・クラスを作成する方向と、既にあるクラスを インターフェイスに対応する方向のどちらでも構いません。

インプリメント・クラスを作成することは、インターフェイス・クラス とほぼ同じ構成をしたコールバック関数を作成することです。 オブジェクト指向言語では、継承を使ってサブクラスを作成することに 相当します。

作成するインプリメント・クラスの宣言
/***********************************************************************
*  <<< [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 は、クラス名に応じて改名する必要があります。

3-2. インプリメント・クラスの記述(2) 〜 インターフェイス・クラスとのリンク

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 マクロの記述は、次の手順で行うと簡単に記述できます。

  1. INF_MACRO_x マクロの記述をコピー
  2. #define の部分をカット
  3. 第2引数に Sub 型を追加
  4. Inf_method1 のような関数名を Sub_method1 に変える

インプリメント・クラスの関数の実装例
/***********************************************************************
*  <<< [Sub_method1] インプリメント・クラスの関数定義 >>>
************************************************************************/
int  Sub_method1( Sub* this )
{
  (普通に書く)

  return  0;
}

3-3. 多態性が必要ないときのマッピングの記述

多態性が必要ない場合、高速化のためにインターフェイス呼び出しの コードを埋め込まなくすることに対応するには、ヘッダファイルの 末尾に次のようなコードを記述します。

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"


4. サブクラスの作成方法

4-1. サブクラスの作成

インプリメント・クラスを作成する際に、 デフォルトのインプリメント・クラス(スーパークラス)があるときは、 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 )

4-2. 流用できない関数について

生成関数(new破壊関数(delete)は、 スーパークラスの関数が流用できません。 必ずオーバーライドして関数を作成してください。 ただし、作成する関数の内部では、初期化関数(init)と 後始末関数(finish)を呼び出すことができるので、 スーパークラスのメンバ変数の初期化と後始末は、 関数を呼び出すだけで済みます。 後は、サブクラス特有のメンバ変数の初期化と後始末を 記述します。


5. パラメタライズド・クラスの作成方法

パラメタライズド・クラスとは、 生成関数(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;

その構造体へのポインタとスキーマ構造体へのポインタを格納する Inf_Pac 型構造体をパラメタライズド・クラス変数として使用します。 生成関数を持つ Inf インターフェイス・クラスに準拠した Sub インプリメント・クラスがあるとき、Sub に対応する パラメタライズド・クラス(値)は、次の関数(getPac)を 使って取得します。

/***********************************************************************
*  <<< [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( &param, 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章


6. メソッド・ポインタ

インターフェイスの関数が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;
}