前へ  |  次へ 

C言語で構造体にインターフェースを実装する

737, 2023-11-09

目次

C言語でインターフェースを実装する

インターフェースとは、クラスがある言語でクラスに対して実装する共通メソッドのことです。
インターフェースは共通化して使用することができます。
特定の共通インターフェースを実装しているクラスA, Bは、インターフェースを取り出すことで同じように扱うことができます。

C言語にはクラスもインターフェースも機能としてありません。
しかし、構造体と関数ポインタ、voidポインタを使うことで似たようなことは実現できます。
今回はこの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は鳥に関する関数をまとめたインターフェースです。

IAnimalIBirdにはともにselfというvoid型のポインタを持っています。
これにはインターフェースを持っている構造体のポインタが格納されます。
void型になっているのは、型を抽象化するためです。

IAnimalwalkrunは関数ポインタです。
これがインターフェースで実装すべきメソッドに当たります。
メソッドとはクラスに属する関数のことを言います。
IBIrdflyも関数ポインタになります。

IAnimalIBirdの名前の先頭のIInterfaceIです。

インターフェースを構造体に実装する

// インターフェースを実装した構造体
// カッコーは動物かつ鳥
typedef struct {
    IAnimal ianimal;  // IAnimalインターフェース
    IBird ibird;  // IBirdインターフェース
    int health;
} Cuckoo;

// インターフェースを実装した構造体
// ライオンは動物
typedef struct {
    IAnimal ianimal;  // IAnimalインターフェース
    int health;
} Lion;

今回はCuckoo(カッコー)とLion(ライオン)という構造体を2つ作ります。
CuckooにはIAnimalIBirdのインターフェースを持たせます。
LionにはIAnimalのインターフェースを持たせます。

この実装はCuckooLionが共通の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.walkcuckoo_walkという関数を入れています。
ここで代入を忘れるとインターフェースが機能しませんので注意が必要です。

またianimal.selfには、コンストラクタで動的にメモリを確保しているselfのアドレスを入れておきます。
これはインターフェースのメソッドを呼び出すときはこのselfを関数に渡してください、という表現になります。

cuckoo_walkcuckoo_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のメソッドであるwalkrunを呼び出しています。
このときに、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関数ではCuckooLionをオブジェクトにしてインターフェスを利用して処理します。
func()func2()にオブジェクトが持つインターフェースのアドレスを渡します。

これらのコードを実行すると以下のような結果になります。

bird walk 90
bird run 60
lion walk 90
lion run 60
bird fly 20

おわりに

今回はC言語でインターフェースを実装してみました。
何か参考になれば幸いです。


 前へ  |  次へ 

この記事のアンケートを送信する