ここでは、C ++プログラマーとして働いていますが、Cで書く必要があることを覚えていなければなりませんでした。 そして、クラスとメソッドが非常に不足していたため、CをC ++に近づける方法を考え始めました。 なんで? そのように、脳を伸ばしてください。
主な願いは次のとおりでした:このようなものがCで動作するようにしたいです:
void print_name( Iface* ptr ) { ptr->print_name(); } void main() { A a; B b; print_name( &a );
実際、AとBは同じ(または複数の)インターフェイスを継承します。 同時に、提供されたポインターに応じて、これらのオブジェクトの仮想メソッドを呼び出すことができます。
誰が最後に何が起こったのか(そして何らかの形で問題が解決した)、C ++で仮想メソッドがどのように実装されるかに関心があるのは誰ですか。
準備完了
当然、私だけがそのような賢い人ではありません。 構造体のメソッドは、関数へのポインターを介して実装されます。 ここに私たちが構築する
ソリューションの 1つがあり
ます 。
そのようなソリューションの欠点は何ですか?
- 構造メソッドのテーブルへの明示的な参照の必要性
- インターフェースの複数の「継承」を実装するための複雑さ(非現実?)
問題をどのように解決しますか?
メソッドのテーブルである別個の構造の代わりに、関数へのポインターを構造に明示的に配置します。 そして、簡単にするために最初から始めます。
複数の「継承」を行う必要がある場合は、最初に1つのインターフェイスのメソッド、次に2番目のメソッドなどを
順番に配置します。
インターフェイスへのポインタを取る関数では、構造体の
最初のインターフェイスメソッドへのポインタを渡し
ます 。 このメソッドには構造固有の実装があるため、構造全体のアドレスの先頭からのこのアドレスのオフセットに依存できます。
ここでの欠点は明らかです。構造の変位を考慮する必要があります。 したがって、構造をパッケージ化するか、何らかの方法でオフセットを認識する必要があります。 私はそれをより簡単にしました:私は得点し、ポインターのサイズとの整合が十分であると思いました。
以下は、プリコンパイル済みヘッダーと、コンパイラCモードで考案されたVS2010の実装です。
行こう!
インターフェース
そのため、2つのインターフェイスを導入します。
最初の
base_ifaceでは、オブジェクトを説明する文字列を返すメソッドを呼び出すことができます。
2番目の
sizible_ifaceを使用すると、オブジェクトのサイズを知ることができます。
例は非常にフェッチされていますが、それはあります。
base_iface.h #pragma once typedef struct base_iface* base_iface_ptr; typedef const char* (*get_name_func_ptr)( base_iface_ptr ); struct base_iface { get_name_func_ptr get_name; }; typedef struct base_iface base_iface;
sizible_iface.h #pragma once typedef struct sizible_iface* sizible_iface_ptr; typedef unsigned int (*get_size_func_ptr)( sizible_iface_ptr ); struct sizible_iface { get_size_func_ptr get_size; }; typedef struct sizible_iface sizible_iface;
クラス
point_tと
d3_point_tの 2つの構造を作成します。これらはそれぞれ2次元および3次元空間のポイントです。 各構造体には、ポイントの座標と、関数への3つのポインター「オブジェクトの名前を取得」、「オブジェクトのサイズを取得」、「ポイントの座標を印刷」が含まれています。
最初のポインターは仮想メソッドであり、
base_ifaceインターフェースの「継承」です。
2番目のポインターは、
sizible_ifaceインターフェースの「継承」という仮想メソッドです。
3番目のポインターは通常のメソッドであり、仮想メソッドではありません。
ヘッダーファイルは次のとおりです。
point.h #pragma once typedef struct point_t* point_ptr; typedef void (*point_print_coordinates_func)( point_ptr ); struct point_t
3d_point.h #pragma once typedef struct d3_point_t* d3_point_ptr; typedef void (*d3_point_print_coordinates_func)( d3_point_ptr ); struct d3_point_t
3Dポイントの場合は、通常の仮想「メソッド」を交換して、仮想構造が異なる構造(内部に要素を配置することでは似ていない)でどのように機能するかを示しました。
Cコンパイラを使用しているため、
point_t *および
d3_point_t *から
base_iface *および
sizible_iface *へ、またはその逆への明示的な型変換を導入する必要があります。 これは、関数がインターフェイスへのポインターを取得し、それらが異なるタイプの構造で機能するという事実を心配しないようにするために行われます。
point_tおよび
d3_point_t構造体の関数の実装を以下に示します。
ポイント #include "std.h" static point_ptr base_iface_to_point( base_iface_ptr ptr );
3d_point.c #include "std.h" static d3_point_ptr base_iface_to_d3_point( base_iface_ptr ptr );
そのため、すべての内部関数には
静的な指定子があり、外部ユーザーには見えません。
仮想メソッドの実装は簡単です。インターフェイスへのポインタを取得して、C ++コンパイラの作業を行います。
- 仮想関数テーブル内の仮想関数のオフセットを計算します(定数を使用してこれを行いました)
- 提供されたポインターからこのオフセットを引き、結果を構造体へのポインターに持ってくる
- 構造全体へのポインタを使用すると、任意の機能を実装できます
上記の例では、「仮想メソッド」の内容はやや緊張しています。 主なことは、これらの「メソッド」内で、構造の
すべての要素にアクセスできることです。
使用例
そして今、私はそのような仮想性を使用する方法の例を示します。
最初に、プリコンパイル済みヘッダーとして使用されるヘッダーファイルを作成する必要があります。
そして、実際には、例自体:
たとえば、説明はほとんどありません。インターフェイスへのポインタを受け入れる2つの関数を発表しました。 したがって、これらの関数で構造体へのポインターを与える前に、これらのポインターを必要な型にキャストします。
結論
VS2010でソースをコンパイルし(Cコンパイラが機能する)、結果のプログラムを実行すると、次のように表示されます。
name = "Null point" size = 8 x = 0, y = 0 name = "Some point" size = 8 x = 1, y = 5 name = "Null 3D point" size = 12 x = 0, y = 0, z = 0 name = "Some 3D point" size = 12 x = 0, y = 1, z = 0 Press any key to exit...
動作します! 「仮想インターフェイス」があり、「多重継承」があり、概念実証があります! 確かに、コードは私たちが望むほど美しくありません。 これは、コンパイラーがC ++で行う作業の一部を行ったという事実によるものです。
- 型変換
- 仮想メソッドのテーブルを操作する
- 「メソッド」内で同じ構造体へのポインターを渡す(同じthis)
しかし、一般に、アプリケーションコードは、私が望んでいたとおりに動作します。 この仮想性はどこに適用できますか? たとえば、性質が異なるが同じインターフェイスを実装する構造のベクトル内。 またはいくつか。
または、暗号化では、ハッシュインターフェイスを使用できます。これには通常、2つの方法があります。データをさらに追加し、入力したデータからハッシュを取得します。
さて、単純に構造を作成し、それを(「。」演算子で)見る機能は、明確な関数名とヒント(転送するパラメーター)を提供する機能(「メソッド」)に感銘を受けます。
関連文献
- ANSI-Cによるオブジェクト指向プログラミング ( xana4ok )
- 美しいコード(主なプログラマーが自分の考えを説明します)、第16章、Linuxカーネルドライバーモデル:一緒に働くことの利点、グレッグクロアハートマン( burjui )