... Serta kata-kata menakutkan lainnya! (c)
Sebelum kita berkenalan dengan beberapa fitur canggih dari sistem objek tipe GLib, kita perlu berbicara tentang sejumlah poin yang tidak kita sentuh dalam dua artikel sebelumnya. Kali ini kita akan berkenalan dengan tipe dasar GObject, berbicara tentang fakta bahwa setiap keturunan dari GObject dasar adalah kesatuan ganda (dan sering triun) dari objek struktur terpisah, di mana makro misterius dibuka di awal file header dan file dengan kode sumber, menggunakan alat apa yang digunakan oleh RTTI lokal yang keras, mengapa GObject dan turunannya memiliki dua destruktor (dan tiga konstruktor), serta sejumlah hal kecil menarik lainnya.

Seluruh siklus tentang GObject:
GObject: dasar-dasarnyaGObject: warisan dan antarmukaGObject: enkapsulasi, instantiasi, introspeksi
Struktur Banyak struktur.
Seperti yang kita ketahui, keturunan GObject dapat diwariskan - turunan dan non-warisan - final. Secara umum, GObject yang dapat diturunkan terdiri dari kombinasi tiga objek: struktur kelas, struktur instance, dan struktur dengan data pribadi.
Dengan struktur kelas, semuanya lebih atau kurang sederhana - dijelaskan dalam file header dan berisi turunan struktur kelas induk dan fungsi pointer - โmetode virtualโ. Merupakan praktik yang baik untuk menambahkan array kecil void pointer ke bidang terakhir struktur untuk memastikan kompatibilitas ABI. Sebuah instance dari struktur seperti itu dibuat dalam satu instance ketika membuat instance pertama dari jenis ini.
typedef struct _AnimalCat AnimalCat; typedef struct _AnimalCatClass AnimalCatClass; typedef struct _AnimalCatPrivate AnimalCatPrivate; struct _AnimalCatClass { GObjectClass parent_class; void (*say_meow) (AnimalCat*); gpointer padding[10]; };
Untuk tipe final, tidak perlu mendefinisikan struktur kelas.
Struktur dengan data pribadi diperlukan untuk objek yang dapat diturunkan. Itu didefinisikan dalam file kode sumber, dan akses ke sana dapat diperoleh melalui fungsi form__cat_get_get_instance_private () yang dihasilkan secara otomatis. Dalam kasus ini, makro di awal file .s akan terlihat seperti G_DEFINE_TYPE_WITH_PRIVATE (NamespaceObject, namespace_object, PARENT_TYPE). Anda dapat menggunakan makro G_DEFINE_TYPE_WITH_CODE (dengan makro G_ADD_PRIVATE disertakan).
#include "animalcat.h" G_DEFINE_TYPE_WITH_PRIVATE(AnimalCat, animal_cat, G_TYPE_OBJECT) /* G_DEFINE_TYPE_WITH_CODE(AnimalCat, animal_cat, G_TYPE_OBJECT, G_ADD_PRIVATE (AnimalCat)) */ struct _AnimalCatPrivate { char* name; double weight; int age; }; static void animal_cat_init(AnimalCat* self) { AnimalCatPrivate* priv = animal_cat_get_instance_private(self); priv->age = 0; priv->name = "Barsik"; /* */ }
Semua data diasumsikan dienkapsulasi. Untuk mengaksesnya, Anda dapat menggunakan pembungkus dan pengambil biasa, tetapi, seperti yang akan kita lihat nanti, GObject menyediakan alat yang jauh lebih kuat untuk ini - properti.
Struktur instance, serta struktur dengan data pribadi, dibuat untuk setiap instance objek. Ini, pada kenyataannya, adalah objek itu sendiri, dengan mana pengguna akhir akan bekerja. Struktur secara otomatis dihasilkan untuk tipe turunan dengan cara makro dari file header, sehingga programmer tidak perlu melakukan ini sendiri. Untuk tipe terakhir, harus dijelaskan secara manual dalam file dengan kode sumber. Karena dalam kasus ini struktur bukan bagian dari antarmuka publik objek, mungkin berisi data pribadi. Jelas, dalam hal ini tidak perlu membuat struktur pribadi yang terpisah.
struct _AnimalTiger { AnimalCat parent; int speed; };
Adapun antarmuka, untuk implementasi mereka perlu mendefinisikan hanya struktur antarmuka, sangat mirip dengan kelas yang biasa. Struktur objek tampilan _AnimalPredator itu sendiri akan dihasilkan secara otomatis.
typedef struct _AnimalPredatorInterface AnimalPredatorInterface; struct _AnimalPredatorInterface { GTypeInterface parent; void (*hunt) (AnimalPredator* self); };
Tabel boks visual:

Deteksi tipe dinamis dalam praktik
Di file header, kami memulai deskripsi tipe baru dengan menggunakan dua makro, yang, pada gilirannya, dikonversi menjadi seluruh set definisi makro. Dalam versi GLib yang lebih lama, perlu untuk menggambarkan secara manual semua alat ini. Mari kita lihat mana yang bisa kita gunakan.
ANIMAL_TYPE_CAT: mengembalikan pengenal integer tipe GType. Makro ini terkait erat dengan sistem tipe GType yang mendasari GObject. Anda pasti akan bertemu dengannya, saya hanya menyebutkannya sehingga jelas dari mana dia berasal. Fungsi dari form animal_cat_get_type () yang menggunakan definisi makro ini secara otomatis dihasilkan dalam file sumber ketika memperluas makro keluarga G_DEFINE_TYPE.
ANIMAL_CAT (obj): dilemparkan ke pointer ke tipe ini. Memberikan kasta yang aman, dan juga melakukan pemeriksaan runtime. Seperti yang Anda lihat, sistem pewarisan di GObject umumnya didasarkan pada fakta bahwa struktur berisi bidang pertama sebagai turunan dari struktur induk, dan oleh karena itu, menurut konvensi pemanggilan C, penunjuk ke objek bertepatan dengan penunjuk ke semua leluhur tempat ia diwariskan. Meskipun demikian, disarankan untuk menggunakan makro yang disediakan, daripada C-cast biasa. Selain itu, dalam beberapa kasus (misalnya, saat melakukan casting ke tipe antarmuka yang diimplementasikan), pemeran gaya-C tidak akan berfungsi sama sekali.
ANIMAL_CAT_CLASS (klass): makro serupa untuk struktur kelas. Konvensi menyarankan untuk tidak menggunakan kelas kata untuk kompatibilitas dengan kompiler C ++.
ANIMAL_IS_CAT (obj): seperti namanya, makro ini menentukan apakah obj adalah pointer ke tipe yang diberikan (dan apakah itu adalah pointer NULL). Merupakan praktik yang baik untuk memulai metode objek dengan cek semacam itu.
void animal_cat_run (AnimalCat *self) { assert(ANIMAL_IS_CAT (self)); g_return_if_fail (ANIMAL_IS_CAT (self)); }
ANIMAL_IS_CAT_CLASS (klass): sama untuk struktur kelas.
ANIMAL_CAT_GET_CLASS (obj): mengembalikan pointer ke struktur kelas yang sesuai.
Serangkaian definisi makro yang serupa juga dihasilkan untuk antarmuka.
ANIMAL_PREDATOR (obj): dilemparkan ke jenis antarmuka.
ANIMAL_IS_PREDATOR (obj): ketik pengecekan.
ANIMAL_PREDATOR_GET_IFACE (obj): mendapatkan struktur antarmuka.
Nama objek dapat diperoleh dengan menggunakan makro G_OBJECT_TYPE_NAME (obj), yang mengembalikan string-si dengan nama tipe.
Makro di awal file sumber G_DEFINE_TYPE dan versi yang diperluas menghasilkan pointer dari bentuk animal_cat_parent_class, yang mengembalikan pointer ke struktur kelas objek induk, serta fungsi dari form animal_cat_get_instance_private (), jika kami menggunakan makro yang sesuai.
Destructors dan fungsi virtual lainnya
Seperti yang kita ingat, saat membuat turunan GObject, fungsi dari form animal_cat_init () diluncurkan. Mereka melakukan peran yang sama dengan C ++ dan konstruktor Java. Dengan destruktor, situasinya lebih rumit.
Manajemen memori di GObject diimplementasikan menggunakan penghitungan referensi. Ketika fungsi g_object_new () dipanggil, jumlah tautan diatur ke satu. Di masa mendatang, kami dapat menambah jumlahnya dengan g_object_ref () dan mengurangi dengan g_object_unref (). Ketika jumlah tautan menjadi nol, proses penghancuran objek, yang terdiri dari dua fase, akan diluncurkan. Pertama, fungsi dispose () dipanggil, yang bisa disebut berkali-kali. Tugas utamanya adalah menyelesaikan referensi melingkar jika perlu. Setelah ini, fungsi finalisasi () dipanggil sekali, di mana segala sesuatu yang biasanya digunakan untuk destruktor dijalankan - memori dibebaskan, deskriptor file terbuka ditutup, dan sebagainya.
Sistem yang sedemikian kompleks dirancang untuk memfasilitasi pembuatan binder untuk bahasa tingkat tinggi, termasuk yang dengan manajemen memori otomatis. Dalam praktiknya, dalam kode C, hanya finalize () yang biasanya digunakan jika objek tersebut dianggap sebagai destruktor.
Fungsi buang () dan finalisasi (), serta sejumlah lainnya, yang akan kita bicarakan nanti, adalah virtual dan didefinisikan dalam GObjectClass.
static void animal_cat_finalize(GObject* obj) { g_print("Buy!\n"); G_OBJECT_CLASS (animal_cat_parent_class)->finalize(obj); } static void animal_cat_class_init(AnimalCatClass* klass) { GObjectClass* obj_class = G_OBJECT_CLASS (klass); obj_class->finalize = animal_cat_finalize; }
Baris terakhir dari fungsi animal_cat_finalize () mungkin membutuhkan penjelasan lebih lanjut. Pointer animal_cat_parent_class ke kelas induk dibuat ketika G_DEFINE_TYPE makro dan versi yang diperluas diperluas. Kami memanggil fungsi yang sesuai dari kelas induk, yang dalam hal ini secara langsung struktur GObjectClass, dan, pada gilirannya, panggilan finalize () dari kelas sebelumnya dalam rantai. Tidak perlu khawatir bahwa kelas induk mungkin tidak mengandung finalisasi () override; GObject akan mengurus ini.
Tetap hanya untuk mengingat bahwa destruktor dipanggil hanya ketika penghitung referensi dipusatkan:
int main(int argc, char** argv) { AnimalCat* cat = animal_cat_new(); g_object_unref(cat); }
Selain dua destruktor, GObjectClass berisi dua konstruktor virtual tambahan. constructor () dipanggil sebelum animal_cat_init () yang sudah dikenal dan langsung membuat turunan dari tipe ini, construct () - after. Tidak mudah untuk menemukan situasi di mana Anda perlu mendefinisikan kembali fungsi-fungsi ini, kecuali tentu saja Anda memutuskan untuk menambal GLib itu sendiri. Dalam dokumentasi, para pengembang memberikan contoh dengan implementasi singleton, tetapi dalam kode nyata saya belum pernah melihat kasus seperti itu. Namun, untuk mencapai fleksibilitas maksimum pada semua tahap siklus hidup fasilitas, pengembang menganggap perlu untuk membuat fungsi-fungsi ini virtual.
Selain itu, GObjectClass berisi fungsi virtual get_property () dan set_property (), yang harus didefinisikan ulang untuk menggunakan fitur-fitur canggih dari tipe dasar GObject dan turunannya sebagai properti di objek mereka sendiri. Kita akan membicarakan ini di artikel selanjutnya.