C言語でオブジェクト指向プログラミング 第2回

皆さん、こんにちは。技術開発グループのn-ozawanです。
明けましておめでとうございます。本年もよろしくお願いいたします。

本題です。
新年あけて早々に実用性0のネタ会です。以前、C言語でオブジェクト指向言語のようにコンストラクタやメンバ変数、メソッドなどをそれっぽく実装してみました。今回は継承やオーバーライドをしてみたいと思います。

C言語で継承やオーバーライドをする

前回のおさらい

第1回から4ヶ月ほど経過していますので、少しおさらいします。ご存知の通りC言語は手続き型プログラミングの1つであり、オブジェクト指向プログラミングではありません。なので、C言語に「クラス」というものはありません。

C言語でクラスっぽいものを実装するには、構造体と関数の使い方を工夫することでした。例えば以下のように実装します。今回はこのコードを継承およびオーバーライドしてみたいと思います。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// メンバ変数
typedef struct {
  char label[256];
} Button;

// 初期化
Button* initButton(Button* button, const char* label) {
  memcpy(button->label, label, sizeof(button->label));
  return button;
}

// コンストラクタ
Button* newButton(const char* label) {
  Button* button = (Button*)malloc(sizeof(Button));
  return initButton(button, label);
}

// メソッド
void pushButton(Button* button) {
  printf("push \"%s\" button!\n", button->label);
}

// メイン処理
int main() {
  Button* b = newButton("hoge");

  pushButton(b);

  free(b);
  return 0;
}

継承

Buttonクラスを拡張してSubmitButtonクラスを実装してみましょう。SubmitButtonクラスはHTMLのFormに入力した内容を、サーバーへ送信するボタンです。なので、SubmitButtonクラスはHTTPメソッドと送信先のURIを持ちます。

// メンバ変数
typedef struct {
  Button parent;    // Buttonクラスを継承
  char method[8];
  char action[256];
} SubmitButton;

// コンストラクタ
SubmitButton* newSubmitButton(const char* label, const char* method, const char* action) {
  SubmitButton* button = (SubmitButton*)malloc(sizeof(SubmitButton));
  initButton(&button->parent, label);
  memcpy(button->method, method, sizeof(button->method));
  memcpy(button->action, action, sizeof(button->action));
  return button;
}

// メイン処理
int main() {
  Button* b = (Button*)newSubmitButton("hoge", "GET", "/example");

  pushButton(b);

  free(b);
  return 0;
}

クラスを継承するには、構造体の定義で、継承元となる構造体(ここではButton)を先頭に定義します。これにより、メイン処理でnewSubmitButton(...)で生成したSubmitButtonのインスタンスをButtonクラスにキャスト変換することが可能になります。

何故、キャスト変換が可能なのでしょうか?Buttonクラスはlabelのみ定義された構造体です。なので、メモリには256byteが確保されます。SubmitButtonクラスはButtonクラスに加えて、methodactionを定義していますので、メモリには計520byteが確保されます。そして、SubmitButton*は確保された520byteの先頭0byte目を指します。これは、Buttonクラスの先頭0byte目と同じことであり、Button*にキャスト変換しても同じ結果になることを意味します。

オーバーライド

先ほどのコードではメイン処理でpushButton(b);を呼び出しており、結果は「push “hoge” button!」と表示されます。このpushButton(...)をオーバーライドしましょう。まずはButtonクラスから見てみましょう。

// ============================================================================
// Button クラス
// ============================================================================

// メンバ変数
typedef struct _Button {
  char label[256];
  void (*pushButton)(struct _Button* button);     // 関数ポインタ
} Button;

// メソッド
void pushButton(Button* button) {
  if (button->pushButton != 0) {
    button->pushButton(button);
  } else {
    printf("push \"%s\" button!\n", button->label);
  }
}

// 初期化
Button* initButton(Button* button, const char* label) {
  memset(button, 0, sizeof(Button));
  memcpy(button->label, label, sizeof(button->label));
  return button;
}

// コンストラクタ
Button* newButton(const char* label) {
  Button* button = (Button*)malloc(sizeof(Button));
  return initButton(button, label);
}

Button構造体にpushButtonの関数ポインタが追加されています。また、pushButton(...)関数においても、関数ポインタpushButtonがNULLでない場合は関数ポインタを、NULLの場合は従来の処理を行うようにしています。次にSubmitButtonクラスを見てみましょう。

// ============================================================================
// SubmitButton クラス
// ============================================================================

// メンバ変数
typedef struct {
  Button parent;    // 継承
  char method[8];
  char action[256];
} SubmitButton;

// メソッド
void pushSubmitButton(Button* button) {
  SubmitButton* submitButton = (SubmitButton*)button;
  printf("Request %s %s\n", submitButton->method, submitButton->action);
}

// コンストラクタ
SubmitButton* newSubmitButton(const char* label, const char* method, const char* action) {
  SubmitButton* button = (SubmitButton*)malloc(sizeof(SubmitButton));
  initButton(&button->parent, label);
  memcpy(button->method, method, sizeof(button->method));
  memcpy(button->action, action, sizeof(button->action));

  button->parent.pushButton = pushSubmitButton;     // オーバーライド
  return button;
}

pushSubmitButton(...)関数を追加しています。そして、コンストラクタでは、親クラスButtonの関数ポインタpushButtonpushSubmitButton(...)関数を設定してあげます。最後にメイン処理を見ましょう。

// ============================================================================
// メイン処理
// ============================================================================
int main() {
  Button* b1 = (Button*)newButton("hoge");
  Button* b2 = (Button*)newSubmitButton("hoge", "GET", "/example");

  pushButton(b1);    // Output: push "hoge" button! 
  pushButton(b2);    // Output: Request GET /example

  free(b1);
  free(b2);
  return 0;
}

比較対象として、newButton(...)関数で生成したインスタンスと、newSubmitButton(...)で生成したインスタンスを用意して、それぞれでpushButton(...)関数を呼び出しています。それぞれで動作が変わっていることが確認できると思います。

Button->pushButtonはNULLを指すようにします。そして、SubmitButton->button.pushButtonpushSubmitButton関数を指すようにします。あとは、pushButton関数で関数ポインタpushButtonがNULLの場合は従来の処理を、NULL以外の場合は関数ポインタpushButtonの処理を呼び出すことでオーバーライドが実現できます。

おわりに

C言語でクラスの継承が出来ました。関数ポインタを利用することにより、メソッドをオーバーライドすることも出来ました。JavaやC#のような柔軟で厳密なカプセル化は難しいですが、これだけ出来ればオブジェクト指向のデザインパターンでフレームワークを作れそうな気がします。難点としては実装が面倒ってことでしょうか。やはりオブジェクト指向でプログラムしたいのであれば、JavaやC#がいいですね。

ではまた。

採用情報

「チームアイオス」入団者募集

〜就活で悩むアナタに来て欲しい〜