非OO言語でどのように継承しますか?
-
10-07-2019 - |
質問
初期のC ++の「コンパイラ」を読みました。実際にC ++コードをCに変換し、バックエンドでCコンパイラを使用しました。私はそれがどのように機能するかについて頭を包むのに十分な技術的知識を持っていますが、言語のサポートなしでクラス継承を行う方法を理解することはできません。
具体的には、いくつかのフィールドを持つクラスを定義し、それから継承するサブクラスの束がそれぞれ独自の新しいフィールドを追加し、それらを関数引数として交換可能に渡すにはどうすればよいですか?特に、C ++でスタックにオブジェクトを割り当てることができるので、どうすればそれを行うことができるので、背後に隠れるためのポインタさえないかもしれません。
注:私が得た最初のカップルの答えは多型性についてでした。ポリモーフィズムと仮想メソッドについてはすべて知っています。 Delphiの仮想メソッドテーブルがどのように機能するかの低レベルの詳細について、一度会議でプレゼンテーションを行ったこともあります。私が疑問に思っているのは、クラスの継承とフィールドであり、ポリモーフィズムではありません。
解決
とにかくCでは、C ++コードがCに変換されたC ++の初期の頃にcfrontで使用されていた方法で、Cfrontを使用します。 >
「クラス」は、コンストラクターの作業を実行する関数を使用して初期化する必要があります。これには、仮想関数のポリモーフィック関数ポインターのテーブルへのポインターの初期化が含まれます。仮想関数呼び出しは、vtbl関数ポインター(関数ポインターの構造を指します-各仮想関数に1つずつ)を介して行う必要があります。
各派生calssの仮想関数構造は、基本クラスの仮想関数構造のスーパーセットである必要があります。
このメカニズムの一部は、マクロを使用して非表示/支援される場合があります。
Miro Samekの初版" C / C ++の実用的なステートチャート" には付録A-「C +-Cでのオブジェクト指向プログラミング」があります。そのようなマクロがあります。これは第2版から削除されたようです。おそらく、それが価値がある以上に面倒だからです。これを行いたい場合は、C ++を使用してください...
Lippmanの"もお読みください。 C ++オブジェクトモデルの内部" では、C ++が舞台裏でどのように機能するかについて詳細に説明しています。
私はあなたが何を求めているのかわかると思います。たぶん。
このようなものはどのように機能しますか:
typedef
struct foo {
int a;
} foo;
void doSomething( foo f); // note: f is passed by value
typedef
struct bar {
foo base;
int b;
} bar;
int main() {
bar b = { { 1 }, 2};
doSomething( b); // how can the compiler know to 'slice' b
// down to a foo?
return 0;
}
さて、言語サポートなしでそれを行うことはできません-手動でいくつかのことをする必要があります(それは言語サポートがないことの意味です):
doSomething( b.base); // this works
他のヒント
基本的に、structs-within-structs。
struct Base {
int blah;
};
struct Derived {
struct Base __base;
int foo;
};
たとえば、 Derived *
を Base *
にキャストする場合、実際には __ base
要素へのポインタを返します。 Derived構造体(この場合は構造体の最初のものです)ので、ポインターは同じである必要があります(ただし、複数継承クラスの場合はそうではありません)。
この場合に blah
にアクセスするには、 derived .__ base.blah
のような操作を行います。
通常、仮想関数は、各オブジェクトの一部である関数ポインターの特別なテーブルを使用して行われます。記録します。
COMがC言語でどのように行うかを以下に示します。私はこれに少しさびていますが、本質はこのように機能します。各「クラス」メンバー変数は単なる構造体です。
struct Shape
{
int value;
};
struct Square
{
struct Shape shape; // make sure this is on top, if not KABOOM
int someothervalue;
};
すべてのメソッドは、実際には通常の関数です。このように
void Draw(Shape * shape,int x,int y)
{
shape->value=10; // this should work even if you put in a square. i think...
}
次に、プリプロセッサを使用して「トリック」します。このようなものを表示するCコード。
Square * square;
square->Draw(0,0); // this doesnt make sense, the preprocessor changes it to Draw(square,0,0);
残念ながら、C ++のような関数呼び出しをプランバニラC呼び出しに解決するためにどのようなプリプロセッサトリックが行われるのかわかりません。
DirectX COMオブジェクトはこの方法で宣言されます。
博士Dobb'sには、このトピックに関する中程度に詳細な記事 Cの単一継承クラス
がありました。 Structs-within-structsは一般的ですが、継承されたフィールドにアクセスするのが面倒です。インダイレクション(例: child-> parent.field
)またはキャスト(((PARENT *)child)-> field
)を使用する必要があります。
私が見た代替案は次のようなものです:
#define COUNTRY_FIELDS \
char *name; \
int population;
typedef struct COUNTRY
{
COUNTRY_FIELDS
} COUNTRY;
#define PRINCIPALITY_FIELDS \
COUNTRY_FIELDS \
char *prince;
typedef struct PRINCIPALITY
{
PRINCIPALITY_FIELDS
} PRINCIPALITY;
これにより、型に継承フィールドへの直接アクセスが与えられます。親のフィールドと継承されたフィールドは同じ場所から始まるため、結果のオブジェクトは引き続き親タイプに安全にキャストできます。
マクロを使用すると、構文を少し改善できます。これは古いPOV-Rayソースで見ました(しかし、それはその後C ++に変換されたと思います)。
この機能がどのように機能するかについての適切なリファレンスが必要な場合は、glib / gdk / gtkオープンソースライブラリをご覧ください。ドキュメントにはかなり優れており、フレームワーク全体がC OOに基づいています。
コンストラクター、セッター、ゲッター、デストラクタを作成して、このthisポインターを明示的に呼び出してオブジェクトをシミュレートできます。
継承は、派生オブジェクトに、派生オブジェクトの構造内のベースオブジェクトへのポインターを含めることによって処理されます。