CでOOスタイルのポリモーフィズムをシミュレートするにはどうすればよいですか?

最初の C++ コンパイラ ("C with classes") は実際に C コードを生成するので、それは間違いなく実行可能です。

基本的に、基本クラスは構造体です。派生構造体は、最初の位置に基本構造体を含める必要があります。これにより、「派生」構造体へのポインターも基本構造体への有効なポインターになります。

typedef struct {
   data member_x;
} base;

typedef struct {
   struct base;
   data member_y;
} derived;

void function_on_base(struct base * a); // here I can pass both pointers to derived and to base

void function_on_derived(struct derived * b); // here I must pass a pointer to the derived class

関数は関数ポインターとして構造体の一部にすることができるため、p->call(p) のような構文が可能になりますが、構造体へのポインターを関数自体に明示的に渡す必要があります。


一般的なアプローチは、関数へのポインターを使用して構造体を定義することです。これは、任意の型で呼び出すことができる「メソッド」を定義します。サブタイプは、この共通構造に独自の関数を設定し、それを返します。

たとえば、Linux カーネルには struct:

があります。
struct inode_operations {
    int (*create) (struct inode *,struct dentry *,int, struct nameidata *);
    struct dentry * (*lookup) (struct inode *,struct dentry *, 
                               struct nameidata *);
    ...
};

登録されたファイルシステムの各タイプは、create に対して独自の関数を登録します。 、 lookup 、および残りの機能。コードの残りの部分は、一般的な inode_operations を使用できます:

struct inode_operations   *i_op;
i_op -> create(...);

C++ は C からそれほど離れていません。

クラスは、VTable と呼ばれる関数ポインターのテーブルへの隠しポインターを持つ構造体です。 Vtable 自体は静的です。型が同じ構造の Vtable を指しているが、ポインタが他の実装を指している場合、ポリモーフィズムが発生します。

コードの乱雑さを避けるために、構造体をパラメーターとして受け取る関数の呼び出しロジックをカプセル化することをお勧めします。

また、構造体のインスタンス化と初期化を関数 (これは C++ コンストラクターと同等) と削除 (C++ のデストラクタ) にカプセル化する必要があります。いずれにせよ、これらは良い習慣です。

typedef struct
{
   int (*SomeFunction)(TheClass* this, int i);
   void (*OtherFunction)(TheClass* this, char* c);
} VTable;

typedef struct
{
   VTable* pVTable;
   int member;

} TheClass;

メソッドを呼び出すには:

int CallSomeFunction(TheClass* this, int i)
{
  (this->pVTable->SomeFunction)(this, i);
}