C におけるオブジェクト指向

C 構文を別のよりオブジェクト指向の言語の構文に近づけようとするために、プリプロセッサを (ab) 使用しないことをお勧めします。最も基本的なレベルでは、単純な構造体をオブジェクトとして使用し、ポインターで渡すだけです:

struct monkey
{
    float age;
    bool is_male;
    int happiness;
};

void monkey_dance(struct monkey *monkey)
{
    /* do a little dance */
}

継承やポリモーフィズムなどを取得するには、もう少し努力する必要があります。構造体の最初のメンバーをスーパークラスのインスタンスにすることで手動継承を行うことができます。その後、基本クラスと派生クラスへのポインターを自由にキャストできます。

struct base
{
    /* base class members */
};

struct derived
{
    struct base super;
    /* derived class members */
};

struct derived d;
struct base *base_ptr = (struct base *)&d;  // upcast
struct derived *derived_ptr = (struct derived *)base_ptr;  // downcast

ポリモーフィズム (つまり、仮想関数) を取得するには、関数ポインターを使用し、オプションで、仮想テーブルまたは vtable とも呼ばれる関数ポインター テーブルを使用します。

struct base;
struct base_vtable
{
    void (*dance)(struct base *);
    void (*jump)(struct base *, int how_high);
};

struct base
{
    struct base_vtable *vtable;
    /* base members */
};

void base_dance(struct base *b)
{
    b->vtable->dance(b);
}

void base_jump(struct base *b, int how_high)
{
    b->vtable->jump(b, how_high);
}

struct derived1
{
    struct base super;
    /* derived1 members */
};

void derived1_dance(struct derived1 *d)
{
    /* implementation of derived1's dance function */
}

void derived1_jump(struct derived1 *d, int how_high)
{
    /* implementation of derived 1's jump function */
}

/* global vtable for derived1 */
struct base_vtable derived1_vtable =
{
    &derived1_dance, /* you might get a warning here about incompatible pointer types */
    &derived1_jump   /* you can ignore it, or perform a cast to get rid of it */
};

void derived1_init(struct derived1 *d)
{
    d->super.vtable = &derived1_vtable;
    /* init base members d->super.foo */
    /* init derived1 members d->foo */
}

struct derived2
{
    struct base super;
    /* derived2 members */
};

void derived2_dance(struct derived2 *d)
{
    /* implementation of derived2's dance function */
}

void derived2_jump(struct derived2 *d, int how_high)
{
    /* implementation of derived2's jump function */
}

struct base_vtable derived2_vtable =
{
   &derived2_dance,
   &derived2_jump
};

void derived2_init(struct derived2 *d)
{
    d->super.vtable = &derived2_vtable;
    /* init base members d->super.foo */
    /* init derived1 members d->foo */
}

int main(void)
{
    /* OK!  We're done with our declarations, now we can finally do some
       polymorphism in C */
    struct derived1 d1;
    derived1_init(&d1);

    struct derived2 d2;
    derived2_init(&d2);

    struct base *b1_ptr = (struct base *)&d1;
    struct base *b2_ptr = (struct base *)&d2;

    base_dance(b1_ptr);  /* calls derived1_dance */
    base_dance(b2_ptr);  /* calls derived2_dance */

    base_jump(b1_ptr, 42);  /* calls derived1_jump */
    base_jump(b2_ptr, 42);  /* calls derived2_jump */

    return 0;
}

そして、それが C でポリモーフィズムを行う方法です。きれいではありませんが、機能します。基本クラスと派生クラスの間のポインター キャストに関する厄介な問題がいくつかありますが、これらは基本クラスが派生クラスの最初のメンバーである限り安全です。多重継承ははるかに困難です。その場合、最初のクラス以外の基本クラス間で大文字と小文字を区別するには、適切なオフセットに基づいてポインターを手動で調整する必要がありますが、これは非常にトリッキーでエラーが発生しやすいものです。

もう 1 つの (トリッキーな) ことは、実行時にオブジェクトの動的な型を変更することです!新しい vtable ポインターを再割り当てするだけです。仮想機能の一部を選択的に変更し、他の機能を保持して、新しいハイブリッド タイプを作成することもできます。グローバル vtable を変更するのではなく、新しい vtable を作成するように注意してください。そうしないと、特定のタイプのすべてのオブジェクトに誤って影響を与えることになります。


私は以前、非常に洗練された方法で実装された C ライブラリを使用したことがありました。彼らは C でオブジェクトを定義する方法を書き、それから継承して、C++ オブジェクトと同じように拡張できるようにしました。基本的な考え方は次のとおりです:

  • 各オブジェクトには独自のファイルがありました
  • パブリック関数と変数は、オブジェクトの .h ファイルで定義されます
  • プライベート変数と関数は .c ファイルにのみ配置されていました
  • 「継承」するには、構造体の最初のメンバーを継承元のオブジェクトとして新しい構造体を作成します

継承を説明するのは難しいですが、基本的には次のとおりです。

struct vehicle {
   int power;
   int weight;
}

次に、別のファイルで:

struct van {
   struct vehicle base;
   int cubic_size;
}

次に、メモリー内にバンを作成し、車両のみを認識しているコードで使用することができます:

struct van my_van;
struct vehicle *something = &my_van;
vehicle_function( something );

それは見事に機能し、.h ファイルは、各オブジェクトで何ができるべきかを正確に定義しました。


C Object System (COS) は有望に思えます (まだアルファ版です)。シンプルさと柔軟性のために利用可能な概念を最小限に抑えようとします:オープン クラス、メタクラス、プロパティ メタクラス、ジェネリック、マルチメソッド、委任、所有権、例外、コントラクト、およびクロージャーを含む統一されたオブジェクト指向プログラミング。それを説明するドラフト ペーパー (PDF) があります。

C の例外は、他のオブジェクト指向言語で見られる TRY-CATCH-FINALLY の C89 実装です。テストスイートといくつかの例が付属しています。

どちらも、C の OOP に多くの作業を行っている Laurent Deniau によるものです。