0/2 はじめに void *
1/2 クラス表現と継承 ←ここ
1/2 2018/07/01 再考しました 再考:クラス表現と継承
2/2 インターフェイス, オーバーライド
C言語でのオブジェクト指向表現、本題に入ります。
今回はクラス表現と継承。
今回言っているクラスはメンバーを持つ普通のクラスの事と思ってください。
インターフェイスクラスだと表現、継承の印象が大分変わるので、次回改めて書きます。
クラスをどう表現するか
例えばJavaのこんなクラスを考えます。
public class HumanClass{
public String name;
private int age;
//この人が年齢、(もしくは好みとして)ビールが飲めるのか?
public bool can_drinking_beer();
//この人がアラサー/アラフォーかの確認
public bool is_alound(int generation);
}
Cで表現してみましょう。
Classメンバーの表現
基本ヘッダーにpublicな構造を定義し、Cファイルにprivateメンバーを書く感じですね。
public methodの前にまずは基本的な表現から。
typedef struct human_class{
char * name;
} human_class;
human_class * human_class_new(コンストラクタパラメータ);
void human_class_init(human_class *, コンストラクタパラメータ);
void human_class_delete(human_class *);
#define HUMAN_CLASS_PUBLIC \
char * name;
struct human_class_implement{
//先頭にpublic
HUMAN_CLASS_PUBLIC
int age;
} human_class_implement;
//プライベートメソッドはCファイル内のstaticな関数で定義
static void is_you_want_to_use_private_metod(XXX);
human_class * human_class_new(コンストラクタパラメータ) {
human_class_implement * instance = malloc(sizeof(human_class_implement));
instance->name = malloc(名前の長さ);
//初期処理
return (human_class *)instance;
}
void human_class_init(human_class *this, コンストラクタパラメータ) {
if(!this->name) {
this->name = malloc(名前の長さ);
}
//初期処理
}
void human_class_delete(human_class *this) {
human_class_implement * instance = (human_class_implement *)this;
//ここではinstance->ageが使えます。
...
free(instance->name);
free(instance);
}
先頭にpublicメンバーを定義し、インスタンスを渡す際にhuman_class
にcastしてあげます。
後はinitメソッドがあると実体の初期化も出来ますね。
human_classクラスと同じ定義でpublicメンバーを書くいいと思います。
使い方については1点注意が。
human_class_newを使わないと、privateメンバーのメモリが確保されない!
というわけで、privateメンバーが必要なら必ずリソースはnew APIで確保しましょう。
Object指向関係なく、大抵の場合はnew, free用のAPIはを用意しておくのが好きです。
理由はリソース管理がhuman_classの実装内で完結していることがわかるからです。
はい、好みです。
4/29追記 コメントでいただきましたが、このクラスをどう表現するか?にも様々な手段、趣向があるんですね。別途時間を見つけて考えて纏めてみたいと思います。
さて、メンバーはいいけどpublicメソッドはどうしましょうか。
手段は2つあるかな。
- 公開APIで表現
- 関数ポインタで表現
どちらも好きですが、私としては1派です。
Public methodの表現1. 公開APIを使う
こんな感じ
typedef struct human_class{
char * name;
} human_class;
human_class * human_class_new(コンストラクタパラメータ);
bool human_class_can_drinking_beer(human_class *);
bool human_class_is_alound(human_class *, int);
void human_class_delete(human_class *);
ヘッダーにそのままpublicメソッドを定義します。実体がないので実体をもらいます。
"human_classのpublicメソッドである"ことがわかる名前の付け方をしておけば、
「human_classはこれで扱うのね。触っていいメンバーはnameなのね」ってのが直感的にわかりやすいと思います。
Public methodの表現2. 関数ポインタで表現
こんな感じ。実装側で定義した関数をcan_drinking_beer
, is_alound
に代入します。
typedef struct human_class{
char * name;
bool (*can_drinking_beer)(human_class *);
bool (*is_alound)(human_class *);
} human_class;
human_class * human_class_new(コンストラクタパラメータ);
void human_class_delete(human_class *, int);
使う側はこんな感じ。
#include "HumanClass.h"
human_class * customer;
int come_customer(void) {
customer = human_class_new(XXX);
if(!customer->can_drinking_beer(customer)) {
printf("お客様、最初の飲み物のご注文はお決まりですか?");
} else {
printf("お客様、最初の飲み物のご注文はお決まりですか?生にいたしますか?");
}
return;
}
int gohome_customer(void) {
//お会計で年齢に悩む
if(customer->is_alound(customer, 40)) {
printf("...そっと40代のボタンを押す店員");
}
human_class_delete(customer);
}
よりオブジェクト指向言語の記載に近いですが、this
ポインタは明示的に渡してあげないといけないんですよね。
好みがありそうですが、
私としては、is_alound
の定義がいらない分human_class_is_alound
を直接呼んだ方が見やすい気がします。
オブジェクト指向言語でも、thisを渡すメソッドなら、staticメソッドで実現しますよね?きっと。
Cでのクラス表現 まとめ
- ヘッダー定義にpublicなクラス構造を定義し、new, delete(, init)を用意する。
- Cファイル内にprivateメンバーを(publicメンバーの)下に追加して拡張。
- newで返す実体は拡張したものをキャストOK
- privateメンバーを利用したい際は、必ずnew, deleteでリソース確保
正直クラスの意識をしなくても、これだけ守って実装するだけでCの実装が一気に見やすくなると思います。
クラス継承をどう表現するか
HumanClassを継承したクラスについて考えましょう
public class FamilyClass extends HumanClass{
public String fatherName;
public String motherName;
public String[] childrenName;
}
家族構成が追加されました。さあどうしましょかね。
privateメンバーがいるなら必ずリソースはそのクラスのnewで確保という前提があるので、
前提として親クラスにprivateメンバーがいない場合の継承について書いていきます。
こちらは2つ紹介
- 親クラスのメンバーを持つ
- 親クラスと同じ定義を先頭に書いてあげる。
1か2で選ぶなら2です。
クラス継承の表現 1: 親クラスのメンバーを持つ
[C 言語によるオブジェクト記述法 COOL ver.2](http://www.sage-p.com/process/cool.htm#6-2:'C 言語によるオブジェクト記述法')がこのイメージです。
human_classは公開APIとしてpublicメソッドを使ってクラスを表現した場合でサンプルを書きます。
typedef struct family_class_{
human_class super;
char * fatherName;
char * motherName;
int children_num;
char ** children;
} family_class;
family_class * family_class_new(XXX);
#define super_init(this, XXX) human_class_init(human_class*)(this), XXXX )
#define super_can_drinking_beer(this) human_class_can_drinking_beer(human_class*)(this))
family_class * family_class_new(XXX) {
family_class * family = (family_class*) malloc(sizeof(family_class));
super_init(family, XXX)
///初期処理色々
//メンバーはsuperをつけてアクセス
printf("name:%s", family->super.name)
//親メソッド呼び出しはマクロでキャストして使う。
if(super_can_drinking_beer(family)) {
printf("ビールが飲めます!");
}
return family;
}
メソッドはいいけど、メンバーアクセスにsuperが付くのが付いちゃうんですよね。
クラス継承の表現 2: 親クラスと同じ定義を先頭に書いてあげる。
HumanClass側のヘッダーに少し工夫を施します。
関数ポインタで表現した方が良さがイメージしやすいのでこちらの改変
typedef struct human_class{
char * name;
bool (*can_drinking_beer)(human_class *);
bool (*is_alound)(human_class *);
} human_class;
#define HUMAN_SUPER \
char * name;\
bool (*can_drinking_beer)(human_class *);\
bool (*is_alound)(human_class *);\
human_class * human_class_new(コンストラクタパラメータ);
void human_class_delete(human_class *, int);
//継承相手向けにhuman_class_init(human_class* this, XXXX )みたいな関数も定義する。
継承用の定義記載をHUMAN_SUPER で用意してあげます。
継承したい人は、同じ定義を先頭に記載。
typedef struct family_class_{
//先頭に定義を羅列。用意されたマクロを書くだけ
HUMAN_SUPER
char * fatherName;
char * motherName;
int children_num;
char ** children;
} family_class;
family_class * family_class_new(XXX);
すると、同じ名前でメンバーアクセスが可能になります!
#define super_init(this, XXX) human_class_init((human_class*)(this), XXXX )
family_class * family_class_new(XXX) {
family_class * family = (family_class*) malloc(sizeof(family_class));
super_init(family, XXX)
///初期処理色々
//メンバーは直書きでOK!
printf("name:%s", family->name)
//親メソッド呼び出しはキャストは必要です。
if(family->can_drinking_beer((human_class*)family)) {
printf("ビールが飲めます!");
}
return family;
}
こちらの方がより継承チックなのでいいかなと。
多重継承はどうするの?
複数のクラスを継承させたいとなると、一気にわかりにくくなります。位置をずらさなきゃいけないんですよね。
human2_class
という別のクラスを継承しましょう。
typedef struct family_class_{
human2_class super2;
human_class super1;
char * fatherName;
char * motherName;
int children_num;
char ** children;
} family_class;
family_class * family_class_new(XXX);
で、human1_class
のメソッドを呼びたいんだけど…
//位置が先頭じゃないので、human_classの場所を与えてあげないといけない。
void call_super1_init(family_class * this, XXX) {
human_class_init(&this->super1, XXXXXXXXX);
}
family_class * family_class_new(XXX) {
family_class * family = (family_class*) malloc(sizeof(family_class));
call_super1_init(family, XXX)
///初期処理色々
...
return family;
}
位置をずらすため、ラッパーを用意しなきゃいけません。
Cでのクラス継承表現 まとめ
1 継承らしく利用出来るのは、親クラスにprivateメンバーがいるときのみ
2 うまく書けば継承を上手に表現できる。
3 多重継承は位置検索のラッパーが必要。
…制約中々ありますね。特に1, 3があるので、私のとしては、無理に継承を実現するメリットがあまりないかなと。
なのでいっそ、「クラス設計の継承をCで実装する時は集約で実現する」
としてしまった方がいいと思っています。
クラス設計と実際のコードにギャップがありますが、そこは
typedef struct family_class_{
human_class * super;/*親クラスとしてhuman_class を保持*/
char * fatherName;
char * motherName;
int children_num;
char ** children;
} family_class;
とか1行コメント入れてあげればいいかと。