主な願いは次のとおりでした:このようなものがCで動作するようにしたいです:
void print_name( Iface* ptr ) { ptr->print_name(); } void main() { A a; B b; print_name( &a ); // "This is A object" print_name( &b ); // "This is B object" }
実際、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 // base_iface sizible_iface { // " " get_name_func_ptr get_name; get_size_func_ptr get_size; // "" point_print_coordinates_func print_coordinates; // int x; int y; }; typedef struct point_t point_t; // point_t point_init( int x, int y ); // point_ptr base_iface_ptr base_iface_ptr point_to_base_iface( point_ptr ptr ); // point_ptr sizible_iface_ptr sizible_iface_ptr point_to_sizible_iface( point_ptr ptr );
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 // base_iface sizible_iface { // "" d3_point_print_coordinates_func print_coordinates; // " " get_name_func_ptr get_name; get_size_func_ptr get_size; // int x; int y; int z; }; typedef struct d3_point_t d3_point_t; // d3_point_t d3_point_init( int x, int y, int z ); // d3_point_ptr base_iface_ptr base_iface_ptr d3_point_to_base_iface( d3_point_ptr ptr ); // d3_point_ptr sizible_iface_ptr sizible_iface_ptr d3_point_to_sizible_iface( d3_point_ptr ptr );
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 ); // " " base_iface static const char* point_get_name( base_iface_ptr ptr ) { static const char* null_point_ptr = "Null point"; static const char* some_point_ptr = "Some point"; point_ptr casted_ptr = base_iface_to_point( ptr ); const char* result = null_point_ptr; if( casted_ptr->x || casted_ptr->y ) { result = some_point_ptr; } return result; } static point_ptr sizible_iface_to_point( sizible_iface_ptr ptr ); // " " sizible_iface static unsigned int point_get_size( sizible_iface_ptr ptr) { point_ptr casted_ptr = sizible_iface_to_point( ptr ); unsigned int size = (unsigned int)( sizeof( casted_ptr->x ) + sizeof( casted_ptr->y ) ); return size; } // "" point_t static void point_print_coordinates( point_ptr ptr ) { printf( "x = %u, y = %u\n", ptr->x, ptr->y ); } // point_t // (private) point_t point_init( int x, int y ) { point_t point; point.get_name = point_get_name; point.get_size = point_get_size; point.print_coordinates = point_print_coordinates; point.x = x; point.y = y; return point; } // enum { num_get_name_offset = 0, num_get_size_offset = sizeof( get_name_func_ptr ), }; // static point_ptr base_iface_to_point( base_iface_ptr ptr ) { return (point_ptr)( (char*)ptr - num_get_name_offset ); } base_iface_ptr point_to_base_iface( point_ptr ptr ) { return (base_iface_ptr)( (char*)ptr + num_get_name_offset ); } static point_ptr sizible_iface_to_point( sizible_iface_ptr ptr ) { return (point_ptr)( (char*)ptr - num_get_size_offset ); } sizible_iface_ptr point_to_sizible_iface( point_ptr ptr ) { return (sizible_iface_ptr)( (char*)ptr + num_get_size_offset ); }
3d_point.c
#include "std.h" static d3_point_ptr base_iface_to_d3_point( base_iface_ptr ptr ); // " " base_iface static const char* d3_point_get_name( base_iface_ptr ptr ) { static const char* null_point_ptr = "Null 3D point"; static const char* some_point_ptr = "Some 3D point"; d3_point_ptr casted_ptr = base_iface_to_d3_point( ptr ); const char* result = null_point_ptr; if( casted_ptr->x || casted_ptr->y || casted_ptr->z ) { result = some_point_ptr; } return result; } static d3_point_ptr sizible_iface_to_d3_point( sizible_iface_ptr ptr ); // " " sizible_iface static unsigned int d3_point_get_size( sizible_iface_ptr ptr) { d3_point_ptr casted_ptr = sizible_iface_to_d3_point( ptr ); unsigned int size = (unsigned int) ( sizeof( casted_ptr->x ) + sizeof( casted_ptr->y ) + sizeof( casted_ptr->z ) ); return size; } // "" d3_point_t static void d3_point_print_coordinates( d3_point_ptr ptr ) { printf( "x = %u, y = %u, z = %u\n", ptr->x, ptr->y, ptr->z ); } // d3_point_t // (private) d3_point_t d3_point_init( int x, int y, int z ) { d3_point_t point; point.get_name = d3_point_get_name; point.get_size = d3_point_get_size; point.print_coordinates = d3_point_print_coordinates; point.x = x; point.y = y; point.z = z; return point; } // enum { num_get_name_offset = sizeof( d3_point_print_coordinates_func ), num_get_size_offset = num_get_name_offset + sizeof( get_name_func_ptr ), }; // static d3_point_ptr base_iface_to_d3_point( base_iface_ptr ptr ) { return (d3_point_ptr)( (char*)ptr - num_get_name_offset ); } base_iface_ptr d3_point_to_base_iface( d3_point_ptr ptr ) { return (base_iface_ptr)( (char*)ptr + num_get_name_offset ); } static d3_point_ptr sizible_iface_to_d3_point( sizible_iface_ptr ptr ) { return (d3_point_ptr)( (char*)ptr - num_get_size_offset ); } sizible_iface_ptr d3_point_to_sizible_iface( d3_point_ptr ptr ) { return (sizible_iface_ptr)( (char*)ptr + num_get_size_offset ); }
そのため、すべての内部関数には静的な指定子があり、外部ユーザーには見えません。
仮想メソッドの実装は簡単です。インターフェイスへのポインタを取得して、C ++コンパイラの作業を行います。
- 仮想関数テーブル内の仮想関数のオフセットを計算します(定数を使用してこれを行いました)
- 提供されたポインターからこのオフセットを引き、結果を構造体へのポインターに持ってくる
- 構造全体へのポインタを使用すると、任意の機能を実装できます
上記の例では、「仮想メソッド」の内容はやや緊張しています。 主なことは、これらの「メソッド」内で、構造のすべての要素にアクセスできることです。
使用例
そして今、私はそのような仮想性を使用する方法の例を示します。
最初に、プリコンパイル済みヘッダーとして使用されるヘッダーファイルを作成する必要があります。
std.h
#pragma once // system #include "stdio.h" // program #include "base_iface.h" #include "sizible_iface.h" #include "point.h" #include "3d_point.h"
標準
#include "std.h"
そして、実際には、例自体:
main.c
#include "std.h" // , base_iface void print_name( base_iface_ptr ptr ) { printf( "name = \"%s\"\n", ptr->get_name( ptr ) ); } // , sizible_iface void print_size( sizible_iface_ptr ptr ) { printf( "size = %u\n", ptr->get_size( ptr ) ); } int main( int argc, const char* argv[] ) { // point_t null_point = point_init( 0, 0 ); point_t some_point = point_init( 1, 5 ); d3_point_t d3_null_point = d3_point_init( 0, 0, 0 ); d3_point_t d3_some_point = d3_point_init( 0, 1, 0 ); // print_name( point_to_base_iface( &null_point ) ); // print_size( point_to_sizible_iface( &null_point ) ); // null_point.print_coordinates( &null_point ); puts( "\n" ); // print_name( point_to_base_iface( &some_point ) ); print_size( point_to_sizible_iface( &some_point ) ); some_point.print_coordinates( &some_point ); puts( "\n" ); print_name( d3_point_to_base_iface( &d3_null_point ) ); print_size( d3_point_to_sizible_iface( &d3_null_point ) ); d3_null_point.print_coordinates( &d3_null_point ); puts( "\n" ); print_name( d3_point_to_base_iface( &d3_some_point ) ); print_size( d3_point_to_sizible_iface( &d3_some_point ) ); d3_some_point.print_coordinates( &d3_some_point ); puts( "\nPress any key to exit...\n" ); getchar(); return 0; }
たとえば、説明はほとんどありません。インターフェイスへのポインタを受け入れる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 )