C言語で構造体にインターフェースを実装する
- 作成日: 2023-11-09
- 更新日: 2023-12-24
- カテゴリ: C言語
C言語でインターフェースを実装する
インターフェースとは、クラスがある言語でクラスに対して実装する共通メソッドのことです。
インターフェースは共通化して使用することができます。
特定の共通インターフェースを実装しているクラスA, Bは、インターフェースを取り出すことで同じように扱うことができます。
C言語にはクラスもインターフェースも機能としてありません。
しかし、構造体と関数ポインタ、voidポインタを使うことで似たようなことは実現できます。
今回はこのC言語のインターフェースを実装してみます。
関連記事
C言語でcharをintに変換する方法
C言語でenumをtypedefして使う【列挙型】
C言語でforeachマクロを実装する方法
C言語でnull判定する方法【NULL, 比較】
C言語でできることを解説!C言語歴16年の開発者が語る
C言語でオブジェクト指向する【単一継承の方法】
C言語でグローバルに関数を使う方法
C言語でシャローコピーとディープコピーを実装する
インターフェースは何の役に立つのか?
インターフェースの利用例としてはゲーム開発がわかりやすいです。
たとえば敵キャラのゴブリンとオークがいたとします。
これらのゴブリンとオークは別々に処理を書くと、面倒です。
たとえば自動で移動する処理なんかは、敵キャラをすべて配列にまとめておいてfor文で処理したいことがあります。
そういうときに、インターフェースの配列を作って、ゴブリンとオークのインターフェ―スへの参照を配列に保存します。
そしてfor文で一括で配列のインターフェースのメソッドを呼び出します。
このようにインターフェースを使うと共通のインターフェースを持っているオブジェクトの処理を一括で処理できるようになります。
ソースコード全文
今回の検証用コードは以下になります。
/* C言語で構造体にインターフェースを実装する例。
* License: MIT
* Since: 2023-11-08
*/
#include <stdio.h>
#include <stdlib.h>
// 動物インターフェースを表現する構造体
typedef struct {
void *self; // オブジェクトのポインタ
void (*walk)(void *); // 実装すべきメソッド
void (*run)(void *); // 実装すべきメソッド
} IAnimal;
// 鳥インターフェースを表現する構造体
typedef struct {
void *self; // オブジェクトのポインタ
void (*fly)(void *); // 実装すべきメソッド
} IBird;
// インターフェースを実装した構造体
// カッコーは動物かつ鳥
typedef struct {
IAnimal ianimal; // IAnimalインターフェース
IBird ibird; // IBirdインターフェース
int health;
} Cuckoo;
// インターフェースを実装した構造体
// ライオンは動物
typedef struct {
IAnimal ianimal; // IAnimalインターフェース
int health;
} Lion;
// Cuckoo
void cuckoo_walk(void *_self) {
Cuckoo *self = (Cuckoo *) _self;
self->health -= 10;
printf("bird walk %d\n", self->health);
}
void cuckoo_run(void *_self) {
Cuckoo *self = (Cuckoo *) _self;
self->health -= 30;
printf("bird run %d\n", self->health);
}
void cuckoo_fly(void *_self) {
Cuckoo *self = (Cuckoo *) _self;
self->health -= 40;
printf("bird fly %d\n", self->health);
}
// デストラクタ
void cuckoo_del(Cuckoo *self) {
free(self);
}
// コンストラクタ
Cuckoo *cuckoo_new(void) {
Cuckoo *self = malloc(sizeof(*self));
self->health = 100;
self->ianimal.self = self;
self->ianimal.walk = cuckoo_walk; // インターフェースの実装
self->ianimal.run = cuckoo_run; // インターフェースの実装
self->ibird.self = self;
self->ibird.fly = cuckoo_fly; // インターフェースの実装
return self;
}
// Lion
void lion_walk(void *_self) {
Lion *self = (Lion *) _self;
self->health -= 10;
printf("lion walk %d\n", self->health);
}
void lion_run(void *_self) {
Lion *self = (Lion *) _self;
self->health -= 30;
printf("lion run %d\n", self->health);
}
// デストラクタ
void lion_del(Lion *self) {
free(self);
}
// コンストラクタ
Lion *lion_new(void) {
Lion *self = malloc(sizeof(*self));
self->health = 100;
self->ianimal.self = self;
self->ianimal.walk = lion_walk; // インターフェースの実装
self->ianimal.run = lion_run; // インターフェースの実装
return self;
}
// インターフェースを利用する関数
// BirdとLionの共通のインターフェースIAnimalのメソッドを呼び出す
void func(IAnimal *impl) {
impl->walk(impl->self);
impl->run(impl->self);
}
// インターフェースを利用する関数
// CuckooのインターフェースIBirdのメソッドを呼び出す
void func2(IBird *impl) {
impl->fly(impl->self);
}
int main(void) {
// オブジェクトの作成
Cuckoo *cuckoo = cuckoo_new();
Lion *lion = lion_new();
// IAnimalインターフェースの利用
func(&cuckoo->ianimal);
func(&lion->ianimal);
// IBirdインターフェースの利用
func2(&cuckoo->ibird);
// オブジェクトの削除
cuckoo_del(cuckoo);
lion_del(lion);
return 0;
}
/* 実行結果
bird walk 90
bird run 60
lion walk 90
lion run 60
bird fly 20
*/
ソースコードの解説
インターフェース
// 動物インターフェースを表現する構造体
typedef struct {
void *self; // オブジェクトのポインタ
void (*walk)(void *); // 実装すべきメソッド
void (*run)(void *); // 実装すべきメソッド
} IAnimal;
// 鳥インターフェースを表現する構造体
typedef struct {
void *self; // オブジェクトのポインタ
void (*fly)(void *); // 実装すべきメソッド
} IBird;
今回のC言語におけるインターフェースは構造体と関数ポインタで実現します。
上記のIAnimal
は動物に関する関数をまとめたインターフェース。
IBird
は鳥に関する関数をまとめたインターフェースです。
IAnimal
とIBird
にはともにself
というvoid型のポインタを持っています。
これにはインターフェースを持っている構造体のポインタが格納されます。
void型になっているのは、型を抽象化するためです。
IAnimal
のwalk
とrun
は関数ポインタです。
これがインターフェースで実装すべきメソッドに当たります。
メソッドとはクラスに属する関数のことを言います。
IBIrd
のfly
も関数ポインタになります。
IAnimal
とIBird
の名前の先頭のI
はInterface
のI
です。
インターフェースを構造体に実装する
// インターフェースを実装した構造体
// カッコーは動物かつ鳥
typedef struct {
IAnimal ianimal; // IAnimalインターフェース
IBird ibird; // IBirdインターフェース
int health;
} Cuckoo;
// インターフェースを実装した構造体
// ライオンは動物
typedef struct {
IAnimal ianimal; // IAnimalインターフェース
int health;
} Lion;
今回はCuckoo
(カッコー)とLion
(ライオン)という構造体を2つ作ります。
Cuckoo
にはIAnimal
とIBird
のインターフェースを持たせます。
Lion
にはIAnimal
のインターフェースを持たせます。
この実装はCuckoo
とLion
が共通のIAnimal
というインターフェースを持っています。
ですので、これら2つの構造体は共通化して処理できるということになります。
共通化して処理というのは、ianimal
のインターフェースを関数に渡せば、そのianimal
のインターフェースのメソッドを型に関係なく呼び出せる、ということです。
health
は体力を表す整数です。
Cuckooの実装
// Cuckoo
void cuckoo_walk(void *_self) {
Cuckoo *self = (Cuckoo *) _self;
self->health -= 10;
printf("bird walk %d\n", self->health);
}
void cuckoo_run(void *_self) {
Cuckoo *self = (Cuckoo *) _self;
self->health -= 30;
printf("bird run %d\n", self->health);
}
void cuckoo_fly(void *_self) {
Cuckoo *self = (Cuckoo *) _self;
self->health -= 40;
printf("bird fly %d\n", self->health);
}
// デストラクタ
void cuckoo_del(Cuckoo *self) {
free(self);
}
// コンストラクタ
Cuckoo *cuckoo_new(void) {
Cuckoo *self = malloc(sizeof(*self));
self->health = 100;
self->ianimal.self = self;
self->ianimal.walk = cuckoo_walk; // インターフェースの実装
self->ianimal.run = cuckoo_run; // インターフェースの実装
self->ibird.self = self;
self->ibird.fly = cuckoo_fly; // インターフェースの実装
return self;
}
Cuckoo
の実装ではデストラクタとコンストラクタを定義します。
デストラクタは破棄用の関数でコンストラクタは構築用の関数です。
コンストラクタでは、インターフェースのメソッド(関数ポインタ)に定義した関数を代入しておきます。
上記の例ではianimal.walk
にcuckoo_walk
という関数を入れています。
ここで代入を忘れるとインターフェースが機能しませんので注意が必要です。
またianimal.self
には、コンストラクタで動的にメモリを確保しているself
のアドレスを入れておきます。
これはインターフェースのメソッドを呼び出すときはこのself
を関数に渡してください、という表現になります。
cuckoo_walk
やcuckoo_run
関数では、引数のvoid型ポインタのself
を型キャストしています。
Cuckoo *self = (Cuckoo *) _self;
こうすることでvoid型のポインタがCuckko
のポインタとして機能するようになります。
つまりメンバ変数などにアクセスできるようになります。
Lionの実装
// Lion
void lion_walk(void *_self) {
Lion *self = (Lion *) _self;
self->health -= 10;
printf("lion walk %d\n", self->health);
}
void lion_run(void *_self) {
Lion *self = (Lion *) _self;
self->health -= 30;
printf("lion run %d\n", self->health);
}
// デストラクタ
void lion_del(Lion *self) {
free(self);
}
// コンストラクタ
Lion *lion_new(void) {
Lion *self = malloc(sizeof(*self));
self->health = 100;
self->ianimal.self = self;
self->ianimal.walk = lion_walk; // インターフェースの実装
self->ianimal.run = lion_run; // インターフェースの実装
return self;
}
Lion
についても同様に実装します。
インターフェースを利用する関数
// インターフェースを利用する関数
// BirdとLionの共通のインターフェースIAnimalのメソッドを呼び出す
void func(IAnimal *impl) {
impl->walk(impl->self);
impl->run(impl->self);
}
// インターフェースを利用する関数
// CuckooのインターフェースIBirdのメソッドを呼び出す
void func2(IBird *impl) {
impl->fly(impl->self);
}
上記の関数は実際にインターフェースを利用する関数です。
func()
ではIAnimal
のインターフェースを受け取り、IAnimal
のメソッドであるwalk
とrun
を呼び出しています。
このときに、walk
等の関数の引数にはimpl->self
を渡しておきます。
func2()
でも同様です。
main関数の実行
int main(void) {
// オブジェクトの作成
Cuckoo *cuckoo = cuckoo_new();
Lion *lion = lion_new();
// IAnimalインターフェースの利用
func(&cuckoo->ianimal);
func(&lion->ianimal);
// IBirdインターフェースの利用
func2(&cuckoo->ibird);
// オブジェクトの削除
cuckoo_del(cuckoo);
lion_del(lion);
return 0;
}
main関数ではCuckoo
とLion
をオブジェクトにしてインターフェスを利用して処理します。
func()
やfunc2()
にオブジェクトが持つインターフェースのアドレスを渡します。
これらのコードを実行すると以下のような結果になります。
bird walk 90
bird run 60
lion walk 90
lion run 60
bird fly 20
おわりに
今回はC言語でインターフェースを実装してみました。
何か参考になれば幸いです。