GObject:継承とインターフェース

前回の記事へのコメントでは 、C ++やその他の高レベル言語が存在するため、GObjectシステムは不要であることがしばしば示唆されました。 コメントで既に説明した純粋に技術的なポイントに加えて、別の側面に触れたいと思います。 おそらく、ほとんどのコメンテーターは、文明を利用し、容赦ない進歩の進展と調和するために、逆行志願者の頑固な抵抗にGLibオブジェクトシステムの意味を理解しています。 これはおそらく、UNIXシステム、GNU、オープンソース、ストールマンのアイデアなどの世界に由来するGlib / GTKの開発の夜明けのケースでした。その世代のハッカーのほとんどはCを好んでいましたが、C ++は比較的若く未発達であり、その使用の利点はそれほど明白ではないようでした。



もちろん、今日、新しいプロジェクトでは、GObjectを使用することのすべてのニュアンスに精通していても、ほとんどの人はより便利で簡潔で安全な言語を使用することを好みます。 ただし、GLib / GTKが存在してから20年以上にわたり、それらを使用して数千のアプリケーションとライブラリが作成され、その多くが世界中の数千人のプログラマーによって今日まで活発に開発されているという事実を見失うことはありません。 新しい機能を追加し、バグをキャッチし、HiDPI画面、Wayland、Vulkanなどの最新のテクノロジーに適応させます。そのようなプロジェクトのコードを読み取る(補足、修正)には、オブジェクト指向拡張の基本的な知識が必要です。私たちが話しているCの場合。



猫の下で歓迎してください。 私たちはいつものように猫で訓練します:)







GObjectに関するサイクル全体:



GObject:基本

GObject:継承とインターフェース

GObject:カプセル化、インスタンス化、イントロスペクション



GObjectの子孫からの継承



一般に、GObjectは単一の継承のみを意味します。 多重継承の場合、Javaのようなインターフェースを使用することが提案されています。これについては、記事の第2部で説明します。



Catから継承するTigerタイプを作成します。これについては、前の記事で説明しました。 今回はそれを最終オブジェクトにします。したがって、将来はそれを継承しないと想定します。



animaltiger.hを作成します。



#ifndef _ANIMAL_TIGER_H_ #define _ANIMAL_TIGER_H_ #include "animalcat.h" G_BEGIN_DECLS #define ANIMAL_TYPE_TIGER animal_tiger_get_type() G_DECLARE_FINAL_TYPE (AnimalTiger, animal_tiger, ANIMAL, TIGER, AnimalCat)
      
      





最終オブジェクトを記述するためにG_DECLARE_DERIVABLE_TYPEの代わりにマクロG_DECLARE_FINAL_TYPEを使用したことに注意してください。また、マクロの最後の引数として「親」を指定しました-AnimalCat。



派生オブジェクトを使用してさらに継承のチェーンを想定する場合、クラスを記述し、親クラスのインスタンスをその最初のフィールドとして作成する必要があります。 この場合、これは必要ありません。



 /* struct _AnimalTigerClass { AnimalCatClass parent_class; }; */
      
      





tigerの新しいインスタンスを返す関数を宣言します。



 AnimalTiger* animal_tiger_new(); G_END_DECLS #endif /* _ANIMAL_TIGER_H_ */
      
      





ここでは、tigerオブジェクトに固有の他のメソッドを宣言できますが、簡単にするために、親から継承したメソッドsay_meowに制限します。 これは、型に固有の実装を提供する仮想関数です。 その後、マクロG_END_DECLSを閉じます。 原則として、ヘッダーファイルをG_BEGIN_DECLS / G_END_DECLSマクロでフレーム化する必要はありません。これらは通常の外部「C」{}に分解され、C ++コンパイラとの互換性のために必要です。 これらは、GLib / GTK +開発者によって採用された単なる実践的なルールです。



ソースコードanimaltiger.cを含むファイルに進みましょう。



 #include <stdio.h> #include "animaltiger.h" struct _AnimalTiger { AnimalCat parent; };
      
      





ヘッダーと標準入出力を接続し、オブジェクトの構造ベースを記述します。 この場合、最終オブジェクトを処理しているため、クラス構造は作成しませんでしたが、メイン構造であるインスタンス構造を明示的に記述する必要があります。



 G_DEFINE_TYPE (AnimalTiger, animal_tiger, ANIMAL_TYPE_CAT)
      
      





初期マクロでは、最後のパラメーターは親のタイプに関する情報を返すマクロです。 覚えているなら、このマクロをヘッダーファイルanimalcat.hで定義しました。



animal_cat_real_say_meow()関数を作成します。



 static void animal_tiger_real_say_meow(AnimalTiger* self) { printf("Tiger say: ARRRRGH!!!\n"); }
      
      





そして2つの主な機能:



 static void animal_tiger_class_init(AnimalTigerClass* self) { printf("First instance of AnimalTiger was created.\n"); AnimalCatClass* parent_class = ANIMAL_CAT_CLASS (self); parent_class->say_meow = animal_tiger_real_say_meow; } static void animal_tiger_init(AnimalTiger* self) { printf("Tiger cub was born.\n"); }
      
      





オブジェクトの最初のインスタンスが作成されたときに呼び出されるanimal_tiger_class_init関数では、ANIMAL_CAT_CLASSマクロを使用して親クラスへのポインターを取得します。 このマクロは、他の多くのマクロと同様に、G_DECLARE_FINAL_TYPEなどのヘッダーファイルでマクロを展開することにより作成されます。 次に、関数を上記の数行で作成した関数に再定義します。 「コンストラクタ」animal_tiger_init()には特別なものはなく、オブジェクトの作成に関するシグナルのみです。



animal_tiger_new()関数では、すべてが親クラスと同様に配置されます。



 AnimalTiger* animal_tiger_new() { return g_object_new(ANIMAL_TYPE_TIGER, NULL); }
      
      





新しいタイプがどのように機能するかを確認しましょう。



 /* main.c */ #include "animaltiger.h" int main(int argc, char** argv) { AnimalTiger* tiger = animal_tiger_new(); animal_cat_say_meow(tiger); return 0; }
      
      





前の記事のMakefileに新しいファイルを追加し、ビルドして実行します。



 First instance of AnimalCat was created. First instance of AnimalTiger was created. Little cat was born. Tiger cub was born. Tiger say: ARRRRGH!!!
      
      





ご覧のとおり、_class_init形式の関数が最初に処理され、次に個々の_initインスタンスのコンストラクターが処理されます。 親オブジェクトのメソッドを呼び出し、子孫のインスタンスへのポインタをパラメータとして渡すと、子孫のオーバーライドされた関数が処理されます。



インターフェース



GObjectには、直接継承に加えて、インターフェイス継承の概念があります。 インターフェースは、インスタンス化不可能なタイプであり、純粋に抽象的なC ++クラスまたはJavaインターフェースに似ています。



私たちのトラに捕食者の特性を取得させましょう-それはAnimalPredatorインターフェースを実装します(もちろん、すべての猫は捕食者ですが、動物学的な微妙さには触れません)。 animalpredator.hファイルを作成します。



 #ifndef _ANIMAL_PREDATOR_H_ #define _ANIMAL_PREDATOR_H_ #include <glib-object.h> G_BEGIN_DECLS #define ANIMAL_TYPE_PREDATOR animal_predator_get_type() G_DECLARE_INTERFACE (AnimalPredator, animal_predator, ANIMAL, PREDATOR, GObject) struct _AnimalPredatorInterface { GTypeInterface parent; void (*hunt)(AnimalPredator*); void (*eat_meat)(AnimalPredator*, int); }; void animal_predator_hunt(AnimalPredator* self); void animal_predator_eat_meat(AnimalPredator* self, int quantity); G_END_DECLS #endif /* _ANIMAL_PREDATOR_H_ */
      
      





ご覧のとおり、ここではすべてが通常の派生子孫GObjectの見出しのように見えます。 いくつかの点に注意しましょう。





また、構造内で、特定のインスタンス化された型によって実装される仮想関数の2つのポインターを作成します。 最後に、これらのポインターの2つのラッパー関数、animal_predator_huntとanimal_predator_eat_meatを宣言します。



ここで、補助ファイルanimalpredator.cを作成します。これは、このインターフェースの実装と混同しないでください。結果のオブジェクトモジュールは、特定の実装のみを起動します。



 #include <stdio.h> #include "animalpredator.h" G_DEFINE_INTERFACE (AnimalPredator, animal_predator, G_TYPE_OBJECT) static void animal_predator_default_init(AnimalPredatorInterface* iface) { printf("The first instance of the object that implements AnimalPredator interface was created\n"); } void animal_predator_hunt(AnimalPredator* self) { AnimalPredatorInterface* iface = ANIMAL_PREDATOR_GET_IFACE (self); iface->hunt(self); } void animal_predator_eat_meat(AnimalPredator* self, int quantity) { AnimalPredatorInterface* iface = ANIMAL_PREDATOR_GET_IFACE (self); iface->eat_meat(self, quantity); }
      
      





すべては単純です:G_DEFINE_TYPEに類似した初期マクロG_DEFINE_INTERFACEは、animalcat.cおよびanimaltiger.cの最初に使用しました。コンストラクター関数animal_predator_default_init。仮想インターフェイス関数の特定の実装を起動するラッパー。 ANIMAL_CAT_CLASSマクロが親クラスの構造を返すように、ANIMAL_PREDATOR_GET_IFACEマクロはインターフェイスのクラス構造を返します。



インターフェースの実装を進めましょう。 animaltiger.cを補足します。



 #include "animalpredator.h" static void animal_tiger_predator_interface_init(AnimalPredatorInterface *iface);
      
      





animal_tiger_predator_interface_init関数は、次のマクロで必要になるため、ファイルの先頭で宣言する必要があります。



マクロG_DEFINE_TYPEを類似のマクロに置き換えますが、より多くの機能があります。 ご覧のとおり、新しい「引数」がここに追加されました。これには、任意のコードに展開されるマクロがいくつか含まれている場合があります。 この場合、G_IMPLEMENT_INTERFACEマクロをそこに配置します。



 G_DEFINE_TYPE_WITH_CODE (AnimalTiger, animal_tiger, ANIMAL_TYPE_CAT, G_IMPLEMENT_INTERFACE (ANIMAL_TYPE_PREDATOR, animal_tiger_predator_interface_init))
      
      





インターフェイスのクラス構造から「プロトタイプ」を実装する2つの関数を追加します。



 static void animal_tiger_predator_hunt(AnimalTiger* self) { printf("Tiger hunts. Beware!\n"); } static void animal_tiger_predator_eat_meat(AnimalTiger* self, int quantity) { printf("Tiger eats %d kg of meat.\n", quantity); }
      
      





最後に、インターフェイスを実装するためのコンストラクター関数を作成します。ここでは、上記で定義した特定の値をインターフェイス構造からのポインターに割り当てます。



 static void animal_tiger_predator_interface_init(AnimalPredatorInterface* iface) { iface->hunt = animal_tiger_predator_hunt; iface->eat_meat = animal_tiger_predator_eat_meat; }
      
      





仕組みを見てみましょう。



 #include "animaltiger.h" #include "animalpredator.h" int main(int argc, char** argv) { AnimalTiger* tiger = animal_tiger_new(); animal_predator_hunt(tiger); animal_predator_eat_meat(tiger, 100500); }
      
      





ビルド、実行:



 First instance of AnimalCat was created. The first instance of the object that implements AnimalPredator interface was created First instance of AnimalTiger was created. Little cat was born. Tiger cub was born. Tiger hunts. Beware! Tiger eats 100500 kg of meat.
      
      





一度に複数のインターフェースを実装することは可能ですか? はい、もちろんです。 GIOの実際のコードは次のとおりです。ここでは、オブジェクトは3つのインターフェイスを同時に実装します。



 static void initable_iface_init (GInitableIface *initable_iface); static void async_initable_iface_init (GAsyncInitableIface *async_initable_iface); static void dbus_object_manager_interface_init (GDBusObjectManagerIface *iface); G_DEFINE_TYPE_WITH_CODE (GDBusObjectManagerClient, g_dbus_object_manager_client, G_TYPE_OBJECT, G_ADD_PRIVATE (GDBusObjectManagerClient) G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init) G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init) G_IMPLEMENT_INTERFACE (G_TYPE_DBUS_OBJECT_MANAGER, dbus_object_manager_interface_init))
      
      





この場合、3つの別個の_interface_init関数が必要になります。 最後の3つのマクロはコンマで区切られていないことに注意してください。これは1つの「引数」です。



インターフェイスの継承はどうですか? ここでの原則は、Javaで使用されているものと似ています。前提条件のメカニズム(型とオブジェクト間の依存関係)がこの問題を解決します。 たとえば、AnimalPredatorインターフェースを実装するには、Eaterインターフェースを実装するか、Mammalなどのオブジェクトから継承する必要があります。 ただし、これらのニュアンスを考慮すると、記事のボリュームが劇的に増加する恐れがあるため、このテキストの範囲外とします。



All Articles