いしな.net

C言語でオブジェクト指向

クラス構造

  1. struct MyClassA_VFTABLE;
  2. /* データ部定義 */
  3. struct MyClassA_OBJECT
  4. {
  5. float x, y, z;
  6. };
  7. struct MyClassA_DATA
  8. {
  9. struct MyClassA_OBJECT MyClassA;
  10. };
  11. /* クラス本体定義 */
  12. struct MyClassA
  13. {
  14. struct MyClassA_VFTABLE *call;
  15. struct MyClassA_DATA data;
  16. };
  17. /* 仮想関数定義 */
  18. struct MyClassA_VFPTR
  19. {
  20. unsigned int offset;
  21. void (*Release)( struct MyClassA *this );
  22. void (*Func1)( struct MyClassA *this, int arg1 );
  23. void (*Func2)( struct MyClassA *this, int arg1 );
  24. };
  25. struct MyClassA_VFTABLE
  26. {
  27. struct MyClassA_VFPTR MyClassA;
  28. };
  29. /* インスタンス作成用 */
  30. struct MyClassA* MyClassA_new( void );
  31. /* メンバ関数 */
  32. void MyClassA_Release( struct MyClassA *this );
  33. void MyClassA_Func1( struct MyClassA *this, int arg1 );
  34. void MyClassA_Func2( struct MyClassA *this, int arg1 );

これは以下のように使う

  1. struct MyClassA *myA;
  2. /* インスタンス作成 */
  3. myA = MyClassA_new();
  4. /* メソッド呼び出し */
  5. myA->call->MyClassA.Func1( myA, 1 );
  6. /* インスタンス破棄 */
  7. myB->call->MyClassB.Release( myB );
2014/01/02(木)

単一継承してみる

MyClassAから派生したMyClassBを作ってみる。
  1. struct MyClassB_VFTABLE;
  2. /* データ部定義 */
  3. struct MyClassB_OBJECT
  4. {
  5. float s, t, u;
  6. };
  7. struct MyClassB_DATA
  8. {
  9. struct MyClassA_OBJECT MyClassA;
  10. struct MyClassB_OBJECT MyClassB;
  11. };
  12. /* クラス本体定義 */
  13. struct MyClassB
  14. {
  15. struct MyClassB_VFTABLE *call;
  16. struct MyClassB_DATA data;
  17. };
  18. /* 仮想関数定義 */
  19. struct MyClassB_VFPTR
  20. {
  21. void (*Func1)( struct MyClassB* this, int arg1 );
  22. };
  23. struct MyClassB_VFTABLE
  24. {
  25. struct MyClassA_VFPTR MyClassA;
  26. struct MyClassB_VFPTR MyClassB;
  27. };
  28. /* インスタンス作成用 */
  29. struct MyClassB* MyClassB_new( void );
  30. /* メンバ関数 */
  31. void MyClassB_Release( struct MyClassB *this );
  32. void MyClassB_Func1( struct MyClassB *this, int arg1 );
  33. void MyClassB_Func2( struct MyClassB *this, int arg1 );

データ構造のイメージはこんな感じになります

MyClassBは以下のように使う

  1. struct MyClassA *myA;
  2. struct MyClassB *myB;
  3. /* インスタンス作成 */
  4. myB = MyClassB_new();
  5. myA = (struct MyClassA*)myB;
  6. /* メソッド呼び出し */
  7. myB->call->MyClassA.Func1( myB, 1 );
  8. myB->call->MyClassA.Func2( myB, 1 );
  9. myB->call->MyClassB.Func1( myB, 1 );
  10. myA->call->MyClassA.Func1( myA, 2 );
  11. myA->call->MyClassA.Func2( myA, 2 );
  12. /* インスタンス破棄 */
  13. myB->call->MyClassA.Release( myB );

12、13行目のように、MyClassAとしても動作する。
warningが出るのは、とりあえず気にしてはいけない。

2014/01/02(木)

MyClassAの実装

さて、それぞれのクラスの実装がどうなっているかというと。
  1. void MyClassA_INIT( void );
  2. void MyClassA_VFTABLE_INIT ( struct MyClassA_VFTABLE *vfptr );
  3. /* MyClassA_VFTABLEのインスタンスは1つだけあれば良い */
  4. struct MyClassA_VFTABLE MyClassA_vfptr;
  5. void MyClassA_INIT( void )
  6. {
  7. MyClassA_VFTABLE_INIT( &MyClassA_vfptr );
  8. }
  9. void MyClassA_VFTABLE_INIT ( struct MyClassA_VFTABLE *vfptr )
  10. {
  11. vfptr->MyClassA.Release = MyClassA_Release;
  12. vfptr->MyClassA.Func1 = MyClassA_Func1;
  13. vfptr->MyClassA.Func2 = MyClassA_Func2;
  14. };
  15. void MyClassA_Release( struct MyClassA *this )
  16. {
  17. struct MyClassA_DATA *data = (struct MyClassA_DATA*)(((char*)this) + this->call->MyClassA.offset);
  18. free( this );
  19. }
  20. void MyClassA_Func1( struct MyClassA *this, int arg1 )
  21. {
  22. puts( "MyClassA_Func1" );
  23. }
  24. void MyClassA_Func2( struct MyClassA *this, int arg1 )
  25. {
  26. puts( "MyClassA_Func2" );
  27. }
  28. struct MyClassA* MyClassA_new( void )
  29. {
  30. struct MyClassA *p = (struct MyClassA*)malloc( sizeof(struct MyClassA) );
  31. /* 各VFTABLEにはポインタを仕込むだけ */
  32. p->call = &MyClassA_vfptr;
  33. return p;
  34. }
2014/01/02(木)

MyClassBの実装

  1. void MyClassB_INIT( void );
  2. void MyClassB_VFTABLE_INIT ( struct MyClassB_VFTABLE *vfptr );
  3. void MyClassA_VFTABLE_INIT ( struct MyClassB_VFTABLE *vfptr );
  4. /* MyClassB_VFTABLEのインスタンスは1つだけあれば良い */
  5. struct MyClassB_VFTABLE MyClassB_vfptr;
  6. void MyClassB_INIT( void )
  7. {
  8. MyClassB_VFTABLE_INIT( &MyClassB_vfptr );
  9. }
  10. void MyClassB_VFTABLE_INIT ( struct MyClassB_VFTABLE *vfptr )
  11. {
  12. /* MyClassAのVFPTRを先に初期化して */
  13. MyClassA_VFTABLE_INIT( (struct MyClassA_VFTABLE *)vfptr );
  14. /* オーバーライドとはVFPTRの関数ポインタ書き換えに他ならない */
  15. vfptr->MyClassA.Release = (void (*)(struct MyClassA*))MyClassB_Release;
  16. vfptr->MyClassA.Func2 = (void (*)(struct MyClassA*,int))MyClassB_Func2;
  17. /* VFPTRのVFPTR初期化 */
  18. vfptr->MyClassB.Func1 = MyClassB_Func1;
  19. };
  20. void MyClassB_Release( struct MyClassB* this )
  21. {
  22. free( this );
  23. }
  24. void MyClassB_Func1( struct MyClassB* this, int arg1 )
  25. {
  26. puts( "MyClassB_Func1" );
  27. }
  28. void MyClassB_Func2( struct MyClassB* this, int arg1 )
  29. {
  30. puts( "MyClassB_Func2" );
  31. }
  32. struct MyClassB* MyClassB_new( void )
  33. {
  34. struct MyClassB *p = (struct MyClassB*)malloc( sizeof(struct MyClassB) );
  35. p->call = &MyClassB_vfptr;
  36. return p;
  37. }
2014/01/02(木)

MyClassAからコピペして作ったMyClassC

“MyClassC”は“MyClassA”とそっくりですが、全く別のクラスです。なんと、floatとintが入れ替わっているのです!!
  1. struct MyClassC_VFTABLE;
  2. /* データ部定義 */
  3. struct MyClassC_OBJECT
  4. {
  5. int x, y, z;
  6. };
  7. struct MyClassC_DATA
  8. {
  9. struct MyClassC_OBJECT MyClassC;
  10. };
  11. /* クラス本体定義 */
  12. struct MyClassC
  13. {
  14. struct MyClassC_VFTABLE *call;
  15. struct MyClassC_DATA data;
  16. };
  17. /* 仮想関数定義 */
  18. struct MyClassC_VFPTR
  19. {
  20. unsigned int offset;
  21. void (*Release)( struct MyClassC *this );
  22. void (*Func1)( struct MyClassC *this, float arg1 );
  23. void (*Func2)( struct MyClassC *this, float arg1 );
  24. };
  25. struct MyClassC_VFTABLE
  26. {
  27. struct MyClassC_VFPTR MyClassC;
  28. };
  29. /* インスタンス作成用 */
  30. struct MyClassC* MyClassC_new( void );
  31. /* メンバ関数 */
  32. void MyClassC_Release( struct MyClassC *this );
  33. void MyClassC_Func1( struct MyClassC *this, float arg1 );
  34. void MyClassC_Func2( struct MyClassC *this, float arg1 );
2014/01/02(木)

MyClassBとMyClassCから派生したMyClassD

SVG表示失敗 こんな感じにするのですが。
ClassD_VFTABLEはこれの先頭を指し、ClassC_VFTABLEはこれのClassC_VFPTRの先頭を指す
  1. struct MyClassD_VFTABLE;
  2. struct MyClassD_VFTABLE2;
  3. /* データ部定義 */
  4. struct MyClassD_OBJECT
  5. {
  6. int dummy;
  7. };
  8. struct MyClassD_DATA
  9. {
  10. struct MyClassA_OBJECT MyClassA;
  11. struct MyClassB_OBJECT MyClassB;
  12. struct MyClassD_OBJECT MyClassD;
  13. };
  14. struct MyClassD_DATA2
  15. {
  16. struct MyClassC_OBJECT MyClassC;
  17. };
  18. /* クラス本体定義 */
  19. struct MyClassD
  20. {
  21. struct MyClassD_VFTABLE *call;
  22. struct MyClassD_DATA data;
  23. struct MyClassD_VFTABLE2 *call2;
  24. struct MyClassD_DATA2 data2;
  25. };
  26. /* 仮想関数定義 */
  27. struct MyClassD_VFPTR
  28. {
  29. void (*Func1)( struct MyClassD* this, int arg1 );
  30. };
  31. struct MyClassD_VFTABLE
  32. {
  33. struct MyClassA_VFPTR MyClassA;
  34. struct MyClassB_VFPTR MyClassB;
  35. struct MyClassD_VFPTR MyClassD;
  36. struct MyClassC_VFPTR MyClassC;
  37. };
  38. struct MyClassD_VFTABLE2
  39. {
  40. struct MyClassC_VFPTR MyClassC;
  41. };
  42. /* インスタンス作成用 */
  43. struct MyClassD* MyClassD_new( void );
  44. /* メンバ関数 */
  45. void MyClassD_Release( struct MyClassD *this );
  46. void MyClassD_Func1( struct MyClassD *this, int arg1 );

MyClassDは以下のように使う。

  1. struct MyClassA *myA;
  2. struct MyClassB *myB;
  3. struct MyClassC *myC;
  4. struct MyClassD *myD;
  5. /* インスタンス作成 */
  6. myD = MyClassD_new();
  7. myA = (struct MyClassA*)myD;
  8. myB = (struct MyClassB*)myD;
  9. /* MyClassCへのキャストはこうしなければならない */
  10. myC = (struct MyClassC*)(&myD->call2);
  11. /* メソッド呼び出し */
  12. myB->call->MyClassA.Func1( myB, 1 );
  13. myB->call->MyClassA.Func2( myB, 1 );
  14. myB->call->MyClassB.Func1( myB, 1 );
  15. myA->call->MyClassA.Func1( myA, 2 );
  16. myA->call->MyClassA.Func2( myA, 2 );
  17. myD->call2->MyClassC.Func1( myC, 2 );
  18. myC->call->MyClassC.Func2( myC, 2 );
  19. /* インスタンス破棄 */
  20. myB->call->MyClassA.Release( myB );

この20行目の呼び出しが正常に行われないと、「継承した」などとは言えないのである。
しかし、その制限によって、MyClassCのメソッドは19行目のように、thisポインタとしてMyClassCへキャストしたものを渡さなければならない。

MyClassAのメソッドは、『VFTABLEポインタの直後からMyClassA用のデータが存在する』ことを要求し、同様にMyClassCのメソッドは、『VFTABLEポインタの直後からMyClassC用のデータが存在する』ことを要求するためである。

これに関連し、もう一つ厄介なことがある。MyClassCのメソッドをオーバーライドした場合だよ!!thisポインタを戻さないとMyClassDのデータ領域にアクセスできないのだ!菱形継承をすると、さらにとんでもないことになるし。

マジ、メンドイのである。だからJavaやC#では多重継承というものが無くなったのだろう。

2014/01/03(金)

インターフェースだとどうなるか?

SVG表示失敗 こうなる。
InterfaceA / InterfaceB / InterfaceD / MyClassのVFTABLEのポインタは同一となるため、1つだけ持っていれば良い。
InterfaceCとInterfaceEは別のVFTABLEへのポインタを持つ必要があるのだが、ここで問題なのがInterfaceEはInterfaceAを継承していることである。
このため、InterfaceE_VFPTRの直前にInterfaceA_VFPTRが存在している必要があるのだああああああッッ!!
この重複したInterfaceA_VFPTRには全く同一のものが入ることになる。
それは、インスタンス作成ルーチンの中に自分で記述する必要がある。
C言語はそれらの補助を一切してくれない。