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
クラスに加えて、method
とaction
を定義していますので、メモリには計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
の関数ポインタpushButton
にpushSubmitButton(...)
関数を設定してあげます。最後にメイン処理を見ましょう。
// ============================================================================
// メイン処理
// ============================================================================
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.pushButton
はpushSubmitButton
関数を指すようにします。あとは、pushButton
関数で関数ポインタpushButton
がNULLの場合は従来の処理を、NULL以外の場合は関数ポインタpushButton
の処理を呼び出すことでオーバーライドが実現できます。

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