[検索]
目次
この文章は、オブジェクト指向言語を使ったことがある人を
対象にしています。
(ヘッダファイル)
/*--------------------------------------------*/ /* * 社員クラス [Staff] */ typedef struct { int id; /* 社員番号という属性 */ char* name; /* 名前という属性 */ } Staff; |
クラス名、および属性名は、『名詞』にします。
クラス名は大文字から始めます。
属性名は小文字から始めます。
どちらも、複数の単語からなる場合は、
2番目以降の単語は大文字から始めます。
たとえば、firstName の N は大文字にします。
クラスは、現実にあるモノや概念またはその役割を、
属性の集りによって表現するものです。
メンバ変数は、直接更新しないで更新をする操作を 使用します。このルールさえ守るだけで、様々なメリットが生まれます。 (2章)
処理に「必要なデータ」がどれかを1つ1つの変数ごとではなく、 「必要なオブジェクト」という意味のある大きな単位で扱うため、 依存関係を調べることが楽になります。 同様に、オブジェクト単位で有効な状態や 共有ロックを意識すれば良くなるのでユーザの負担が減ります。 (ただし、オブジェクト単位で良くなるためには、 オブジェクトが表現する概念を適切に割り当てる必要があります。)
クラスは型であり、型から多くの実体
(
(ヘッダファイル)
/*--------------------------------------------*/ /* * 社員クラス [Staff] */ typedef struct { int id; char* name; } Staff; void Staff_init( Staff* this, int id, const char* name ); /* 初期化する操作 */ int Staff_getId( Staff* this ); /* id を返す操作 */ |
操作名は、『動詞』にします。
クラス名と操作名の間には '_' (アンダーライン)を
付けます。操作名は属性名と同じく小文字から始めます。
人間の言葉は、動詞(操作)より名詞(クラス)の方が
分類用の言葉が多いので、関数を特定のクラスに所属させると、
膨大になりがちな関数を適切に分類することができるようになります。
第1引数は、必ずそのクラスへのポインタ型にします。
API のような低レベルの関数では、オブジェクトが構造体変数でなく、
識別番号やハンドルになっていることがあります。その場合、
第1引数を識別番号やハンドルにします。
操作を適切に設計すると、属性や派生属性(プロパティ)や 状態を設定/参照する操作になります(→ 5章)。 そうなると、プロセスを意識する必要がなくなるので ユーザの負担が減ります。
1章で説明したように、メンバ変数は変更しないで、
操作の関数を通して状態を変更します。
これを
(ヘッダファイル)
/*--------------------------------------------*/ /* * 社員クラス [Staff] */ typedef struct { int id; char* name; } Staff; extern int Staff_n; /* 社員数というクラス属性 */ #define Staff_m; /* 社員数の最大という定数 */ |
操作の記述と同様に、属性名の前にクラス名を付け、
間に '_'(アンダーライン)を付けます。
属性名、定数名は、操作名と同様に小文字から始めます。
この種類の操作を一覧します。
コンストラクタの代わりに初期化操作を用います。(ヘッダファイル)
/*--------------------------------------------*/ /* * 社員クラス [Staff] */ typedef struct { int id; char* name; } Staff; void Staff_init( Staff* this, int id, char* name ); |
(ソースファイル)
#include "staff.h" void Staff_init( Staff* this, int id, char* name ) { Member_init( &this->member ); /* メンバ変数の初期化 */ /* その他 メンバ変数の初期化 */ this->id = id; strcpy( this->name, name ); } |
初期化操作の第1引数はは、ローカル変数オブジェクトでも
malloc された変数オブジェクトでも構いません。
速度の最適化を考えて、スタティック・メモリから確保した領域を
指定することもできます。
初期化関数内でメンバ変数に初期化が必要なものは、
メンバ変数の初期化が重たいものと後始末関数があるものです。
この初期化操作など以下で説明する操作は、さまざまな初期化バリエーションを 作ることが出来ます。
void Staff_initByHuman( Staff* this, Human* human ); |
(ソースファイル)
void main() { Staff a; Staff* b = malloc( sizeof(Staff) ); Staff_init( &a, 1, "Mark" ); Staff_init( b, 2, "Jon" ); } |
ハードウェアをカプセル化したオブジェクトは、初期化のタイミングで ハードウェアの独特な設定を行う必要があります。しかし、 その設定を引数に与えてしまうとオブジェクトを汎用的に 使用できなくなります。 初期化からコールバックされる関数を定義して、 そのコールバック関数の中で(デフォルトの)設定をすれば、 設定を引数に与えなくて済むようになります。
初期化操作や生成操作やその他の操作で、malloc したり、
ファイルを開いたりした場合、
C++ 言語ではデストラクタの内部で free したり
ファイルを閉じたりします。
C 言語では、後始末操作を用います。
ヒープに対する削除操作は、StdPlus_delete マクロを使って後始末関数を流用できます。
(ヘッダファイル)
/*--------------------------------------------*/ /* * 社員クラス [Staff] */ typedef struct { int id; char* name; } Staff; void Staff_finish( Staff* this ); /* 後始末 */ |
(ソースファイル)
#include "staff.h" void Staff_finish( Staff* this ) /* 後始末 */ { Member_finish( &this->member ); /* メンバ変数の後始末 */ /* その他 後始末の処理 */ } |
後始末操作または削除操作は、必要なければ作る必要ありません。
デストラクタと異なり、自動的に呼ばれることはないので、
明示的に後始末操作または削除操作を呼び出す必要があります。
(ソースファイル)
void main() { Staff a; Staff_init( &a, 1, "Mark" ); Staff_finish( &a ); /* 後始末操作を明示的に呼ぶ */ } |
構造化例外処理をサポートする環境では、初期化済みかどうかの フラグを用意して、finally 節に後始末操作を記述します。
(ソースファイル)
void main() { Staff a; bool bA = false; /* a が初期化されたかどうか */ __try { Staff_init( &a, 1, "Mark" ); bA = true; } __finally { if ( bA ) Staff_finish( &a ); } } |
再コンパイル不要インターフェイスでは、以下の点を注意してください。
この種類の操作を一覧します。
生成操作は、内部で malloc した新しいオブジェクトのアドレスを返します。
削除操作は、内部で free します。
ポインタ型を、末尾に _P を付けた名前に型定義すれば、
動的バインディング・インターフェイスと互換性を持たせることができます。
(ヘッダファイル)
/*--------------------------------------------*/ /* * 社員クラス [Staff] */ typedef struct { int id; char* name; } Staff; typedef Staff* Staff_P; /* 動的バインディング・インターフェイスとの互換 */ Staff* Staff_new( int id, char* name ); void Staff_delete( Staff* this ); |
(ソースファイル)
#include "staff.h" Staff* Staff_new( int id, char* name ); { Staff* this = malloc( sizeof(Staff) ); /* 自分自身の生成 */ if ( this == NULL ) error(); Staff_init( this, id, name ); return this; } void Staff_delete( Staff* this ) { Staff_finish( this ); free( this ); /* 自分自身の削除 */ } |
malloc, free を使わなくても、オリジナルの記憶領域管理を使用したり、 キャッシュを参照するようにしても構いません。
その他一般的な操作において、 その実装方法がマクロで行われているときは、 関数で実装する関数操作に変更する必要があります。 これは、マクロをコンパイルするとその実装内容のコードが 操作を呼び出す側にも埋め込まれるため、 構造体の構造や操作の処理内容といった内部的な変更があったときに 再コンパイルが必要になるためです。
(ヘッダファイル)
/**************************************** * <<< 設定 >>> *****************************************/ /* #define STAFF_USE_FUNC */ /* _f, _m の付かない標準インターフェイスを関数版に割り当てる */ /**************************************** * <<< [Staff] >>> *****************************************/ int Staff_macro( Staff* this ); int Staff_macro_m( Staff* this ); /* マクロ版 */ int Staff_macro_f( Staff* this ); /* 関数版 */ /**************************************** * <<< [Staff_macro] >>> *****************************************/ #ifdef STAFF_USE_FUNC #define Staff_macro Staff_macro_f #else #define Staff_macro Staff_macro_m #endif #define Staff_macro_m( this ) \ /* Staff_macro の実装部分をここに移動 */ |
(ソースファイル)
#include "staff.h" /**************************************************** * <<< [Staff_macro_f] Staff_macro の関数版 >>> *****************************************************/ int Staff_macro_f( Staff* this ) { return Staff_macro_m( this ); } |
処理効率優先インターフェイスから移植するときは、次の注意が必要です。
DLL(Dynamic Link Library) や動的リンクドライバを作成するときは、 再コンパイルも再リンクも不要となる、 動的バインディング・インターフェイスにします。
動的バインディング・インターフェイスでは、以下の点を注意してください。
この種類の操作を一覧します。
使用する側の互換性を保つために、インターフェイスは再コンパイル不要インターフェイスと 同じにして、処理効率優先インターフェイスを別クラスに変更します。 処理効率優先インターフェイスのクラスの名前は、Staff_Imp のように _Imp を付けます。 オブジェクトへのポインタを Staff_P のように _P を付けます。
動的バインディングを実現するには、シンボル名の文字列から
操作関数のアドレスを内部で動的に取得する必要があります。
これを生成操作の内部で行います。
RPC(Remote Procedure Call) のシステムが使えれば、
その呼び出し方法を内部で調節することにより、サポートすることができます。
(ヘッダファイル)
/************************************************************************************ * <<< [Staff] Staff クラスの動的バインディング・インターフェイス(スタブ) >>> *************************************************************************************/ #ifdef STAFF_STATIC_LINK typedef Staff_Imp* Staff_P; #else typedef Inf Staff_P; /* Inf 構造体は第7章を参照 */ #endif Staff_P Staff_new( const char* className ); void Staff_delete( Staff_P ); /* 以下は内部用 */ extern Staff_Sc Staff_sc; /*********************************************************************** * <<< [Staff_delete] 削除操作(マッピングの例) >>> ************************************************************************/ #if defined( STAFF_STATIC_LINK ) #defnie Staff_delete( this ) Staff_Imp_delete( this ) #elif defined( STAFF_REMOTE_LINK ) #defnie Staff_delete( this ) RemoteProcCall( Staff_sc.delete, this ) #else /* dynamic link */ #defnie Staff_delete( this ) Staff_sc.delete( this ) #endif |
(ソースファイル)
/*********************************************************************** * <<< [Staff_new] 生成操作 >>> ************************************************************************/ bool Staff_Sc_inited = false; /* スキーマを初期化したかどうか */ Staff_Sc Staff_sc; /* 動的バインディング・スキーマ */ Staff_P Staff_new( const char* dllPath ) { Staff_P this; #ifdef STAFF_STATIC_LINK this = malloc( sizeof(Staff_Imp) ); /* 自分自身の生成 */ if ( this == NULL ) error(); Staff_Imp_init( this, id, name ); /* _Imp クラスに変更した処理効率優先インターフェイスをコール */ #else /* スキーマの初期化(GetProcAddress を現在のタスクで必要なためここで初期化する) */ if ( ! Staff_Sc_inited ) { HINSTANCE DLL = Inf_AfxLoadLibrary( dllPath ); Staff_sc->new = Inf_GetProcAddress( DLL, Staff_Imp_new ); Staff_sc->delete = Inf_GetProcAddress( DLL, Staff_Imp_delete ); Staff_sc->method = Inf_GetProcAddress( DLL, Staff_Imp_method ); : : (その他の操作の関数ポインタも並べる) Staff_Sc_inited = true; }; /* ハンドルの初期化 */ this.sc = &Staff_sc; #ifdef STAFF_RIMOTE_LINK this.obj = RemoteProcCall( Staff_sc->new ); #else this.obj = Staff_sc->new(); /* 再コンパイル不要インターフェイスの生成操作を呼び出す */ #endif #endif return this; } |
静的バインディングとの互換性のために、関数ポインタを取得するマクロを以下のように用意します。
(Inf モジュール・ヘッダファイル)
HINSTANCE Inf_AfxLoadLibrary_imp( const char* path ); /*********************************************************************** * <<< [Inf_AfxLoadLibrary] モジュールをロードする >>> ************************************************************************/ #ifdef STAFF_STATIC_LINK #define Inf_AfxLoadLibrary( path ) NULL #else #define Inf_AfxLoadLibrary( path ) Inf_AfxLoadLibrary_imp( path ) #endif /*********************************************************************** * <<< [Inf_GetProcAddress] 文字列から関数のアドレスを返す >>> ************************************************************************/ #ifdef STAFF_STATIC_LINK #define Inf_GetProcAddress( DLL, func ) func #else #define Inf_GetProcAddress( DLL, func ) GetProcAddress( DLL, #func ) #endif |
(Inf モジュール・ソースファイル)
/*********************************************************************** * <<< [Inf_AfxLoadLibrary_imp] Inf_AfxLoadLibrary の関数部分 >>> ************************************************************************/ HINSTANCE Inf_AfxLoadLibrary_imp( const char* path ); { HINSTANCE DLL = AfxLoadLibrary( path ); if ( DLL == NULL ) error(); return DLL; } |
この種類の操作を一覧します。
オブジェクトの状態を取得する
int Staff_getAge( Staff* this ) { return Date_getDiffYear( this->birth, getToday() ); } |
オブジェクトの状態を設定する
void Staff_setBirth( Staff* this, Date* birth ) { *this->birth = *birth; this->age = Date_getDiffYear( this->birth, getToday() ); } |
リンカブル・オブジェクトのリンクの状態を設定するときは、
リンクの状態を取得するときは、
一般的な状態の取得と特に扱いが変わらないので、
同じ名前(get)を使用します。
void Staff_linkFriend( Staff* this, Staff* friend ) { this->friend = friend; } |
イベントドリブン・スタイルで行う場合、イベント(自分以外の
オブジェクトの状態変化)の通知を受ける
void MyWindow_onKey( MyWindow* this, int key ) { } void main() { Keybord_addOnKeyHandler( keybord, Method_Ptr(myWindow, MyWindow_onKey) ); or Keybord_addTarget( keybord, MyWindow_inf_Target(myWindow) ); } |
イベントハンドラはユースケースに相当するので、 イベントハンドラ(関数)を汎用的に呼び出せるように再利用しようとすると、 逆に依存性が強く修正に弱いプログラムになるので注意してください。 なぜなら、イベントハンドラはいつ呼び出されるかを決めているだけで、 期待する動作を決めないためです。 再利用する場合は、イベントハンドラの内容を(構造化して) コピー&ペーストします。
モード関数(do)は、アプリケーション・プログラムの あるモードが終了するまで戻ってこない関数です。 この関数の内部では、ユーザや各種イベントの入力と 各イベントハンドラへのマッピングを行います。
イベントハンドラやモード関数などの全体的なパターンは、 別紙『プログラミングスタイル CERT』を参照してください。(ヘッダファイル)
typedef struct { int a; } PartsA; typedef struct { int b; } PartsB; typedef struct { PartsA a; PartsB b; int m; } Main; |
小 → 大 | ||
---|---|---|
継承 | 多重継承 | 重複継承 |
動的継承 |
C 言語は継承がありませんが、スーパークラスを集約することで、
仮想的に継承を実装することができます。
これは、(継承的な)
(ヘッダファイル)
/************************************************************ * <<< [Super] Super クラス >>> *************************************************************/ typedef struct { int a; } Super; void Super_method( Super*, int n ); /************************************************************ * <<< [Sub] Super クラスから継承したクラス >>> *************************************************************/ typedef struct { Super inherit_Super; /* スーパークラスは第1メンバ変数にする */ /* メンバ変数名は、inherit+クラス名 */ int b; } Sub; |
サブクラスは、スーパークラスにキャストして、スーパークラスの操作を
実行することができますが、コンパイラがプロトタイプ宣言による型チェックの
警告を出すので、マクロを定義します。
(このマクロにより、クラスを多重継承に対応するよう変更に(後述)したとき、
そのクラスを使っているソースを修正しなくて済むようにもなります。)
(ソースファイル)
/************************************************************ * <<< [Super] Super クラス >>> *************************************************************/ #define Super_method( this, n ) Super_method_imp( (Super*)(this), n ) void Super_method_imp( Super*, int n ); /* 内部用 */ /************************************************************ * <<< [Sub] Super クラスから継承したクラス >>> *************************************************************/ typedef { Super inherit_Super; int n; } Sub; /************************************************************ * <<< 実行サンプル >>> *************************************************************/ void main() { Sub sub; Sub_init( &sub ); Super_method( &sub, 1 ); /* スーパークラスの操作の呼出し */ } |
ただし、上記の引数はマクロで隠すことができるので互換性は保たれます。
(ソースファイル)
/************************************************************ * <<< [Super2] 多重継承に対応した Super2 クラス >>> *************************************************************/ void Super2_method( Super2*, int n ); /* 公開用(プロトタイプ宣言は無くてもよい) */ #define Super2_method( sub, n ) \ Super2_method_imp( &(sub)->inherit_Super2, sub, sizeof(*(sub)), n ) void Super2_method_imp( Super2*, void* obj, int size, int n ); /* 内部用 */ /************************************************************ * <<< [Sub] Super2 から多重継承(仮想継承)したクラス >>> *************************************************************/ typedef struct { /* 多重継承的な委譲 */ Super1 inherit_Super1; Super2 inherit_Super2; /* virtual 継承, Super2 クラスのメンバ変数 */ int b; } Sub; /************************************************************ * <<< 実行サンプル >>> *************************************************************/ void main() { Sub sub; Sub_init( &sub ); sub.b = 0; sub.super.a = 1; /* Super クラスの属性にアクセス */ Super2_method( &sub, 1 ); } |
(重複継承は、COOL のオリジナル用語です。)
(ソースファイル)
typedef struct { /* 多重継承的な委譲 */ Super inheritFor_Refer1; /* Refer1 クラスまたは refer1 インスタンス用 */ Super inheritFor_Refer2; /* Refer2 クラスまたは refer2 インスタンス用 */ int b; } Sub; |
重複継承は、『同時』に関連を持つ複数のクラスやインスタンスが
同じスーパークラスのときに使います。(異なるスーパークラスの場合は
多重継承で済みます)。
重複継承に適するものは、リスト構造の次ポインタや双方向ポインタなどがあります。
重複継承をサポートするクラスが持つ操作関数には、
どのスーパークラス・メンバ変数を使うかを渡す必要があります。
操作関数名に Ofs を付け、第2引数にメンバ変数名を指定する
操作関数をクラスに追加します。
(ソースファイル)
typedef struct { /* 重複継承的な委譲 */ Super inheritFor_Refer1; /* Refer1 クラスまたは refer1 インスタンス用 */ Super inheritFor_Refer2; /* Refer2 クラスまたは refer2 インスタンス用 */ int b; } Sub; #define Super_methodOfs( this, inherit, refer ) \ Super_method_imp( &(this)->inherit, this, sizeof(*(this)), refer ) void Super_method_imp( Super2*, void* obj, int size, Refer* refer ); /* 内部用 */ /* 多重継承用の操作関数とまったく同じものを流用できます */ void main() { Sub sub; Refer refer1; Refer refer2; Super_methodOfs( &sub, inheritFor_Refer1, refer1 ); #define mb2 inheritFor_Refer2 /* メンバ変数名の短縮 */ Super_methodOfs( &sub, mb2, refer2 ); Super_methodOfs( &sub, mb2, refer2 ); #undef mb2 } |
少しでも互換性を保つ場合は、スキーマ(グローバル変数)に
メンバ変数に関するオフセット情報を持つようにして、
引数にメンバ変数名を指定しなくて済む
「重複継承の多態性」(→第7章)を使用してください
(ただし、グローバル変数を参照する分だけ遅くなります)。
(ソースファイル)
/************************************************************ * <<< [Sub] Super クラスが活性化したときのクラス >>> *************************************************************/ typedef struct { /* 動的継承的な委譲 */ Super* inherit_Super; /* スーパークラスへのポインタ */ または Super_Inf inherit_Super; /* スーパークラスへのインターフェイス */ int n; } Sub; /************************************************************ * <<< 実行サンプル >>> *************************************************************/ void main() { Super data; /* スーパークラス(静的データ) */ Sub actData; /* サブクラス(活性化状態) */ Super_init( &data ); Sub_init( &actData, &data, ... ); /* スーパークラスを引数に指定、活性化 */ Sub_finish( &actData ); /* 非活性化 */ Super_finish( &data ); } |
動的継承は、静的なデータをスーパークラスとして、
そのデータを活性化状態(処理中)にするときにサブクラスを使用します。
サブクラスは、スーパークラスを所有しません(後始末しません)。
この動的継承的な委譲は、活性化した後に元の型に戻る場合に適しています。
戻らない場合は、通常の単一継承的な委譲(スーパークラスの実体を持つ)を
したクラスに型変換することで済むことが多いです。(メモリ的にも速度的にも)
Sub1 クラスと Sub2 クラスが、Super クラスからインターフェイス継承しているとし、
共通のインターフェイス Super_method 操作の第1引数(オブジェクト)のクラスによって
Sub1_method 操作と Sub2_method 操作の呼び出しを切り替えるようにしたいとして
以下に説明を続けます。
Super クラスのインターフェイス(Super_Inf)は、次のような構造体です。
(ヘッダファイル)
/************************************************************ * <<< [Super_Inf] Super クラスの多態インターフェイス >>> *************************************************************/ typedef struct { Super_Sc* sc; /* 多態操作スキーマのアドレス(後述) */ void* obj; /* オブジェクトのアドレス */ } Super_Inf; void Super_Inf_method( Super_Inf this ); /* Super_Inf のポインタではない */ |
インターフェイスの型名は、Xxx_Inf のようにスーパークラス名の後に、
_Inf を付けたものにします。
Super クラスのインターフェイスは、a または b に対するポインタのように扱います。
(つまり a, b の実体を削除するタイミングに注意する)。
操作の第1引数(オブジェクト)は、多態インターフェイスの
構造体へのポインタ型ではなく構造体そのものにします。
他のクラスのメンバ変数も Super_Inf クラスへのポインタではなく構造体そのものにします。
typedef struct { Sub1* a; } Refer; | → | typedef struct { Super_Inf inf; /* ポインタではない */ } Refer; |
(ヘッダファイル)
/********************************************************* * <<< [Super_Sc] Super クラスの多態操作スキーマ >>> **********************************************************/ typedef struct { void (*method)(); /* 関数ポインタ */ : : (その他の操作の関数ポインタも並べる) int size; /* サブクラスのメモリサイズ */ } Super_Sc; |
関数ポインタ method には、Sub1_method 関数のアドレスか
Sub2_method 関数のアドレスが格納されます。
これを利用して、Super_Inf_method 操作関数を実装します。
(ソースファイル)
void Super_Inf_method( Super_Inf this ) { if ( this.sc->method == NULL ) error(); this.sc->method( this.obj ); } |
サブクラス(Sub1 クラスと Sub2 クラス)には、
Super クラスのインターフェイス取得関数を用意します。
(ソースファイル)
Inf_Sc Sub1_sc; Super_Inf Sub1_inf_Super( Sub1* this ) { static int initialized = 0; Inf inf; if ( ! initialized ) { Sub1_sc.method = Sub1_method; : : (その他の操作の関数ポインタも並べる) (実装されていない関数には、NULL を代入します) initialized = 1; }; inf.sc = &Sub1_sc; inf.obj = this; return inf; } |
Super_Inf クラスを使うときは、次のようにします。
(ソースファイル)
typedef struct { /* Super クラスを参照するクラス */ Super_Inf super; } Refer; void Refer_init( Refer*, Super_Inf super ); void main() { Refer refer; Sub1 sub1; Sub2 sub2; Super_Inf super; Sub1_init( &sub1 ); super = Sub1_inf_Super( &sub1 ); /* インターフェイスを取得、構造体をそのまま代入 */ Refer_init( &refer, super ); Refer_run( &refer ); Sub1_init( &sub2 ); super = Sub2_inf_Super( &sub2 ); Refer_init( &refer, super ); Refer_run( &refer ); } void Refer_run( Refer* this ) { Super_Inf_method( this->super ); /* 多態 */ } |
Refer_run 関数の中の Super_Inf_method 関数は、this->super メンバ変数が
ポイントしているクラスに応じて Sub1_method 関数か
Sub2_method 関数のどちらかが呼ばれます。
void main( int argc, char* argv[] ) { Xxx_Sc* subClass; /* パラメタライズド・クラス */ subClass = Yyy_getSc_Xxx_Sc(); func( subClass, argv[1] ); } void func( Xxx_Sc* subClass, char* name ) { Xxx_Inf inf = Xxx_Inf_new( subClass, name ); /* スーパークラスのインターフェイスを取得 */ Xxx_Inf_method( inf, "a" ); Xxx_Inf_delete( inf ); } |
多態生成をサポートするクラスとそのインターフェイスの定義の例を示します。
/********************************************************** * インターフェイス (Xxx_Inf) ***********************************************************/ Xxx_Inf Xxx_Inf_new( Xxx_Sc* subClass, char* name ); void Xxx_Inf_delete( Xxx_Inf ); Xxx_Inf Xxx_Inf_init( void* union_adr, Xxx_Sc* subClass, char* name ); void Xxx_Inf_method( Xxx_Inf, char* s ); struct _Xxx_Sc { /* スキーマ */ Xxx_Inf (*new)( Xxx_Sc* subClass, char* name ); /* → Yyy_new() */ void (*delete)( void* obj ); /* → Yyy_delete() */ void (*init)( void* obj, Xxx_Sc* subClass, char* name ); /* → Yyy_init() */ void (*method)( void* obj, char* s ); /* → Yyy_method() */ }; /********************************************************** * インスタンスのクラス (Yyy) ***********************************************************/ Yyy* Yyy_new( Xxx_Sc* yyyClass, char* name ); void Yyy_delete( Yyy* ); void Yyy_init( Yyy*, Xxx_Sc* yyyClass, char* name ); void Yyy_method( Yyy*, char* s ); Xxx_Sc* Yyy_getSc_Xxx_Sc( void ); Xxx_Inf Yyy_inf_Xxx_Inf( Yyy* ); |
インターフェイスの実装の例を示します。
Xxx_Inf Xxx_Inf_new( Xxx_Sc* subClass, char* name ) { ASSERT( subClass->new != NULL ); return subClass->new( subClass, name ); } void Xxx_Inf_delete( Xxx_Inf this ) { ASSERT( subClass->delete != NULL ); this.sc->delete( this.obj ); } Xxx_Inf Xxx_Inf_init( void* union_adr, Xxx_Sc* subClass, char* name ) { Xxx_Inf this; ASSERT( subClass->init != NULL ); subClass->init( union_adr, subClass, name ); this.obj = union_adr; this.sc = subClass; return this; } void Xxx_Inf_method( Xxx_Inf this, char* s ) { ASSERT( subClass->method != NULL ); this.sc->method( this.obj, s ); } |
(ソースファイル)
/********************************************************* * <<< [Super2_Inf] Super2 クラスのインターフェイス >>> **********************************************************/ typedef struct { void* obj; Super2_Sc* sc; } Super2_Inf; typedef struct { Offset offset; /* 派生クラス(Sub)の先頭から Super2 型メンバ変数へのオフセット */ int size; /* 派生クラスのメモリサイズ */ } Super2_Sc; void Super2_Inf_method( Super2_Inf this, int n ) { Super2_method_imp( &Offset_ref( this.sc->offset, this.obj, Super2 ) ), this.obj, this.sc->size, n ); } /********************************************************* * <<< [Sub] Sub クラス >>> **********************************************************/ typedef struct { /* 多重継承的な委譲 */ Super1 inherit_Super1; Super2 inherit_Super2; /* virtual 継承, Super2 クラスのメンバ変数 */ int b; } Sub; #define Sub_inf_Super2( this ) \ Sub_inf_Super2_imp( this, Offset_init2( this, inherit_Super2 ) ) Inf_Sc Sub_sc; Inf Sub_inf_Super2_imp( Sub* this, Offset offset ) { static int initialized = 0; Inf inf; if ( ! initialized ) { Sub_sc.method = Sub1_method; : : (その他の操作の関数ポインタも並べる) (実装されていない関数には、NULL を代入します) Sub_sc.offset = offset; /* スーパークラス・メンバ変数へのオフセット */ initialized = 1; }; inf.sc = &Sub_sc; inf.obj = this; return inf; } /********************************************************* * <<< [Refer] Super2 を参照するクラス >>> **********************************************************/ typedef struct { int data; Super2_Inf super2; /* super2 クラスから派生するインスタンスへの多態インターフェイス */ } Refer; /********************************************************* * <<< 実行サンプル >>> **********************************************************/ void main() { Refer ref; Sub sub; Super2_Inf inf; Sub_init( &sub ); inf = Sub_inf_Super2( &sub ) Refer_init( &ref, inf ); Refer_sub( &ref ); } void Refer_run( Refer* this ) { Super2_Inf_method( this->super2, 3 ); } |
(ソースファイル)
#define Sub_inf_Super( this, inherit ) \ Sub_inf_Super_imp( this, Offset_init2( this, inherit ) ) typedef struct { /* Super を参照するクラス */ int data; Super_Inf super; /* super クラスから派生するインスタンスへの多態インターフェイス */ } Refer; void main() { Refer ref; Sub sub; Super_Inf inf; Sub_init( &sub ); inf = Sub_inf_Super( &sub, inheritFor_Sub1 ); Refer_init( &ref, inf ); Refer_run( &ref ); } void Refer_run( Refer* this ) { Super_Inf_method( this->super, 3 ); } |
コンテナを設計するときは、多態する位置に注意します。
多態する位置によって要素多態とコンテナ多態(スキーマ所有)と
コンテナ多態(スキーマ非所有)とに分けられ、
それらは別のコンテナ・クラスになります(実装部分は一部共有できます)。
/******************************************************* * <<< 要素多態のコンテナ >>> ********************************************************/ typedef struct { : } Container; void Container_init( Container* ); void Container_add( Container*, Elem* elem, elem_type ); void Container_finish( Container* ); /* 内部関数 */ void Container_add_imp( Container*, Elem_Inf ); #define Container_add( this, elem, elem_type ) \ Container_add_imp( this, elem_type##_inf_Elem( elem ) ) /* 実行サンプル */ void sample() { Container con; Sub sub; Container_init( &con ); Container_add( &con, &sub, Sub ); Container_finish( &con ); } |
/******************************************************* * <<< コンテナ多態のコンテナ(スキーマ所有) >>> ********************************************************/ typedef struct { Container3 inherit_Container3; /* 後記 */ Elem_Sc* elem_sc; } Container2; void Container2_init( Container2*, elem_type ); void Container2_add( Container2*, Elem* elem ); void Container2_finish( Container2* ); /* 内部関数(スキーマ非所有タイプの内部関数を流用できます) */ void Container3_init_imp( this, Offset elem_offset, int elem_size, (*Elem_method)() ); void Container3_add_imp( Container*, Elem* elem, void* elem_top, int elem_size, (*Elem_method)() ); void Container3_finish_imp( this, Offset elem_offset, int elem_size, (*Elem_method)() ); #define Container2_init( this, elem_type ) \ ( (this)->elem_sc = elem_type##_Sc_get_Elem(), \ Container3_init_imp( this, (inf).elem_sc->offset, sizeof(elem_type), (this)->elem_sc->method ) ) #define Container2_add( this, elem ) \ Container3_add_imp( this, &Offset_ref( (inf).sc->offset, (inf).obj, Elem ), \ elem, sizeof(*(elem)), (inf).sc->method ) #define Container2_finish( this ) \ Container3_finish_imp( this, (inf).elem_sc->offset, (inf).elem_sc->size, (inf).elem_sc->method ) /* 実行サンプル */ void sample() { Container2 con; Sub sub; Container2_init( &con, Sub ); Container2_add( &con, &sub ); Container2_finish( &con ); } |
コンテナ多態(スキーマ所有)の内部関数からコンテナ多態(スキーマ非所有)の 内部関数に呼び出すときは、第1引数をキャストしてください。 (両コンテナの間で多重継承することは無いため)
コンテナ多態(スキーマ非所有): コンテナのすべての操作関数の引数に、「要素の型指定」や「スーパークラス・メンバ変数」や 「要素の操作関数指定」を指定すると、インターフェイスの互換性が低くなる代わりに、 スキーマを参照しない分、メモリ使用量、速度が良くなります。
/******************************************************* * <<< コンテナ多態のコンテナ(スキーマ非所有) >>> ********************************************************/ typedef struct { : } Container3; void Container3_init( Container3*, elem_type, (*Elem_init)() ); void Container3_add( Container3*, Elem* elem, (*Elem_add)() ); void Container3_finish( Container3*, elem_type, (*Elem_finish)() ); void Container3_initOfs( Container3*, elem_type, elem_inherit, (*Elem_init)() ); void Container3_addOfs( Container3*, Elem* elem, elem_inherit, (*Elem_add)() ); void Container3_finishOfs( Container3*, elem_type, elem_inherit, (*Elem_finish)() ); /* 内部関数(インスタンスの先頭アドレスやメモリサイズや関数ポインタを引数に持つ) */ void Container3_init_imp( this, Offset elem_offset, int elem_size, (*Elem_init)() ); void Container3_add_imp( Container*, Elem* elem, void* elem_top, int elem_size, (*Elem_add)() ); void Container3_finish_imp( this, Offset elem_offset, int elem_size, (*Elem_finish)() ); #define Container3_init( this, elem_type, Elem_init ) \ Container3_init_imp( this, Offset_init( elem_type, inherit_Elem ), sizeof(elem_type), Elem_init ) #define Container3_add( this, elem, Elem_add ) \ Container3_add_imp( this, &(elem)->inherit_Elem, elem, sizeof(*(elem)), Elem_add ) #define Container3_finish( this, elem_type, Elem_finish ) \ Container3_finish_imp( this, Offset_init( elem_type, inherit_Elem ), sizeof(elem_type), Elem_finish ) #define Container3_initOfs( this, elem_type, elem_inherit, Elem_init ) \ Container3_init_imp( this, Offset_init( elem_type, elem_inherit ), sizeof(elem_type), Elem_init ) #define Container3_addOfs( this, elem, elem_inherit, Elem_add ) \ Container3_add_imp( this, &(elem)->elem_inherit, elem, sizeof(*(elem)), Elem_add ) #define Container3_finishOfs( this, elem_type, elem_inherit, Elem_finish ) \ Container3_finish_imp( this, Offset_init( elem_type, elem_inherit ), sizeof(elem_type), Elem_finish ) /* 実行サンプル */ void sample() { Container3 con; Sub sub; Container3_init( &con, Sub, Sub_init ); Container3_add( &con, &sub, Sub_add ); Container3_finish( &con, Sub, Sub_finish ); Container3_initOfs( &con, Sub, inherit_Super2, Sub_init ); Container3_addOfs( &con, &sub, inherit_Super2, Sub_add ); Container3_finishOfs( &con, Sub, inherit_Super2, Sub_finish ); } |
基本的に1モジュールに1クラスですが、
Staff クラス Staff_Card クラス |
このように、関数名やグローバル変数名の先頭をモジュール名(=ファイル名)に
『統一』することで、その実装部分がどのファイルにあるのかすぐに分かります。
逆に MS-DOS は、ファイル名に 8文字という制限が
あるので、クラス名も 8文字の短縮形にします。
クラスに属することができない関数を
カテゴリは、Math_sin() のようにこれまで説明したクラスと同様に記述します。
ただし、初期化(生成)、後始末(削除)の操作は無く、
第1引数はオブジェクトではありません。
sin() のようにモジュール名を付けないと、
その実装部分がどのファイルにあるのかすぐに分からなくなるなどの
不都合が生じます。