... sowie andere gruselige Worte! (c)
Bevor wir uns mit einigen erweiterten Funktionen des Objektsystems vom Typ GLib vertraut machen, müssen wir über eine Reihe von Punkten sprechen, die wir in den beiden vorherigen Artikeln nicht angesprochen haben. Dieses Mal werden wir uns mit dem Grundtyp von GObject vertraut machen und über die Tatsache sprechen, dass jeder Nachkomme des Grund-GObject eine doppelte Einheit (und oft dreieinig) von separaten Strukturobjekten ist, in die mysteriöse Makros am Anfang von Header-Dateien und -Dateien mit Quellcode geöffnet werden Für welche Tools arbeitet das harte lokale RTTI, warum haben GObject und seine Nachkommen zwei Destruktoren (und drei Konstruktoren) sowie eine Reihe anderer interessanter Kleinigkeiten.

Der ganze Zyklus über GObject:
GObject: die GrundlagenGObject: Vererbung und SchnittstellenGObject: Kapselung, Instanziierung, Selbstbeobachtung
Strukturen Viele Strukturen.
Wie wir wissen, können Nachkommen von GObject vererbt werden - ableitbar und nicht vererbt - endgültig. Im Allgemeinen besteht ein ableitbares GObject aus einer Kombination von drei Objekten: einer Klassenstruktur, einer Instanzstruktur und einer Struktur mit privaten Daten.
Bei einer Klassenstruktur ist alles mehr oder weniger einfach - es wird in der Header-Datei beschrieben und enthält eine Instanz der Klassenstruktur und Funktionszeiger des übergeordneten Elements - „virtuelle Methoden“. Es wird empfohlen, dem letzten Feld der Struktur ein kleines Array von Leerzeigern hinzuzufügen, um die ABI-Kompatibilität sicherzustellen. Eine Instanz einer solchen Struktur wird in einer Instanz erstellt, wenn die erste Instanz dieses Typs erstellt wird.
typedef struct _AnimalCat AnimalCat; typedef struct _AnimalCatClass AnimalCatClass; typedef struct _AnimalCatPrivate AnimalCatPrivate; struct _AnimalCatClass { GObjectClass parent_class; void (*say_meow) (AnimalCat*); gpointer padding[10]; };
Für endgültige Typen muss keine Klassenstruktur definiert werden.
Für ableitbare Objekte wird eine Struktur mit privaten Daten benötigt. Es ist in der Quellcodedatei definiert und der Zugriff darauf kann über eine automatisch generierte Funktion der Form animal_cat_get_instance_private () erfolgen. In diesem Fall sollte das Makro am Anfang der .s-Datei wie folgt aussehen: G_DEFINE_TYPE_WITH_PRIVATE (NamespaceObject, namespace_object, PARENT_TYPE). Sie können das Makro G_DEFINE_TYPE_WITH_CODE verwenden (einschließlich des Makros G_ADD_PRIVATE).
#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"; /* */ }
Es wird angenommen, dass alle Daten gekapselt sind. Um auf sie zuzugreifen, können Sie die üblichen Wrapper verwenden - Getter und Setter, aber wie wir später sehen werden, bietet GObject ein viel leistungsfähigeres Werkzeug für diese Eigenschaften.
Die Instanzstruktur sowie die Struktur mit privaten Daten werden für jede Instanz des Objekts erstellt. Dies ist in der Tat das Objekt selbst, mit dem der Endbenutzer hauptsächlich arbeiten wird. Die Struktur wird automatisch für ableitbare Typen mithilfe eines Makros aus der Header-Datei generiert, sodass der Programmierer dies nicht selbst tun muss. Für endgültige Typen muss es manuell in einer Datei mit Quellcode beschrieben werden. Da in diesem Fall die Struktur nicht Teil der öffentlichen Schnittstelle des Objekts ist, kann sie private Daten enthalten. In diesem Fall muss natürlich keine separate private Struktur erstellt werden.
struct _AnimalTiger { AnimalCat parent; int speed; };
Für die Schnittstellen muss für ihre Implementierung nur die Schnittstellenstruktur definiert werden, die der üblichen Klasse 1 sehr ähnlich ist. Die Struktur des Ansichtsobjekts _AnimalPredator selbst wird automatisch generiert.
typedef struct _AnimalPredatorInterface AnimalPredatorInterface; struct _AnimalPredatorInterface { GTypeInterface parent; void (*hunt) (AnimalPredator* self); };
Visuelle Krippentabelle:

Dynamische Typerkennung in der Praxis
In den Header-Dateien haben wir die Beschreibung des neuen Typs mit der Verwendung von zwei Makros begonnen, die wiederum in eine ganze Reihe von Makrodefinitionen konvertiert werden. In älteren Versionen von GLib musste das gesamte Toolkit manuell beschrieben werden. Mal sehen, welche davon wir verwenden können.
ANIMAL_TYPE_CAT: Gibt eine Ganzzahlkennung vom Typ GType zurück. Dieses Makro ist eng mit dem GType-Typsystem verwandt, das GObject zugrunde liegt. Sie werden ihn auf jeden Fall treffen, ich habe ihn nur erwähnt, damit klar war, woher er kommt. Funktionen der Form animal_cat_get_type (), die diese Makrodefinition verwenden, werden automatisch in der Quelldatei generiert, wenn Makros der G_DEFINE_TYPE-Familie erweitert werden.
ANIMAL_CAT (obj): In einen Zeiger auf diesen Typ umwandeln. Bietet sichere Castes und führt auch Laufzeitprüfungen durch. Wie Sie sehen können, basiert das Vererbungssystem in GObject im Allgemeinen auf der Tatsache, dass die Strukturen das erste Feld als Instanz der übergeordneten Struktur enthalten. Daher stimmt der Zeiger auf das Objekt gemäß den C-Aufrufkonventionen mit dem Zeiger auf alle Vorfahren überein, von denen es geerbt wird. Trotzdem ist es ratsam, das bereitgestellte Makro anstelle des üblichen C-Casts zu verwenden. Darüber hinaus funktioniert in einigen Fällen (z. B. beim Casting in einen implementierten Schnittstellentyp) ein Cast im C-Stil überhaupt nicht.
ANIMAL_CAT_CLASS (klass): Ein ähnliches Makro für Klassenstrukturen. Die Konvention schreibt vor, die Wortklasse aus Gründen der Kompatibilität mit C ++ - Compilern nicht zu verwenden.
ANIMAL_IS_CAT (obj): Wie der Name schon sagt, bestimmt dieses Makro, ob obj ein Zeiger auf diesen Typ ist (und ob es ein NULL-Zeiger ist). Es wird empfohlen, die Methoden des Objekts mit einer solchen Überprüfung zu starten.
void animal_cat_run (AnimalCat *self) { assert(ANIMAL_IS_CAT (self)); g_return_if_fail (ANIMAL_IS_CAT (self)); }
ANIMAL_IS_CAT_CLASS (klass): Gleiches gilt für Klassenstrukturen.
ANIMAL_CAT_GET_CLASS (obj): Gibt einen Zeiger auf die entsprechende Klassenstruktur zurück.
Ein ähnlicher Satz von Makrodefinitionen wird auch für Schnittstellen generiert.
ANIMAL_PREDATOR (obj): In Schnittstellentyp umwandeln.
ANIMAL_IS_PREDATOR (obj): Typprüfung.
ANIMAL_PREDATOR_GET_IFACE (obj): Abrufen der Schnittstellenstruktur.
Der Objektname kann mit dem Makro G_OBJECT_TYPE_NAME (obj) abgerufen werden, das eine si-Zeichenfolge mit dem Typnamen zurückgibt.
Die Makros am Anfang der Quelldatei G_DEFINE_TYPE und ihre erweiterten Versionen generieren einen Zeiger der Form animal_cat_parent_class, der einen Zeiger auf die Klassenstruktur des übergeordneten Objekts sowie eine Funktion der Form animal_cat_get_instance_private () zurückgibt, wenn wir das entsprechende Makro verwendet haben.
Destruktoren und andere virtuelle Funktionen
Wie wir uns erinnern, werden beim Erstellen eines GObject-Nachkommen Funktionen der Form animal_cat_init () gestartet. Sie spielen dieselbe Rolle wie die C ++ - und Java-Konstruktoren. Bei Destruktoren ist die Situation komplizierter.
Die Speicherverwaltung in GObject wird mithilfe der Referenzzählung implementiert. Wenn die Funktion g_object_new () aufgerufen wird, wird die Anzahl der Links auf eins gesetzt. In Zukunft können wir ihre Anzahl mit g_object_ref () erhöhen und mit g_object_unref () verringern. Wenn die Anzahl der Verknüpfungen Null wird, wird der Prozess der Zerstörung des Objekts, der aus zwei Phasen besteht, gestartet. Zunächst wird die Funktion dispose () aufgerufen, die mehrfach aufgerufen werden kann. Seine Hauptaufgabe besteht darin, bei Bedarf Zirkelverweise aufzulösen. Danach wird die Funktion finalize () einmal aufgerufen, in der alles ausgeführt wird, wofür Destruktoren normalerweise verwendet werden - Speicher wird freigegeben, offene Dateideskriptoren werden geschlossen und so weiter.
Ein derart komplexes System wurde entwickelt, um die Erstellung von Ordnern für Hochsprachen zu erleichtern, einschließlich solcher mit automatischer Speicherverwaltung. In der Praxis wird im C-Code normalerweise nur finalize () verwendet, wenn das Objekt die Existenz eines Destruktors annimmt.
Die Funktionen dispose () und finalize () sowie eine Reihe anderer Funktionen, über die wir später sprechen werden, sind virtuell und in GObjectClass definiert.
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; }
Die letzte Zeile der Funktion animal_cat_finalize () scheint weitere Erläuterungen zu erfordern. Der animal_cat_parent_class-Zeiger auf die übergeordnete Klasse wird erstellt, wenn das Makro G_DEFINE_TYPE und seine erweiterten Versionen erweitert werden. Wir rufen die entsprechende Funktion aus der übergeordneten Klasse auf, die in diesem Fall direkt eine GObjectClass-Struktur ist, und rufen ihrerseits finalize () der vorherigen Klasse in der Kette auf. Sie müssen sich keine Sorgen machen, dass die übergeordnete Klasse möglicherweise keine finalize () - Überschreibungen enthält. GObject kümmert sich darum.
Es bleibt nur daran zu erinnern, dass der Destruktor nur aufgerufen wird, wenn der Referenzzähler auf Null gesetzt ist:
int main(int argc, char** argv) { AnimalCat* cat = animal_cat_new(); g_object_unref(cat); }
Neben zwei Destruktoren enthält GObjectClass zwei zusätzliche virtuelle Konstruktoren. Konstruktor () wird vor dem bereits bekannten animal_cat_init () aufgerufen und erstellt direkt eine Instanz dieses Typs, construct () - after. Es ist nicht einfach, eine Situation zu finden, in der Sie diese Funktionen neu definieren müssen, es sei denn, Sie entscheiden sich natürlich dafür, GLib selbst zu patchen. In der Dokumentation geben die Entwickler ein Beispiel für die Implementierung eines Singletons, aber in echtem Code habe ich solche Fälle noch nie gesehen. Um jedoch in allen Phasen des Lebenszyklus der Anlageninstanz maximale Flexibilität zu erreichen, hielten es die Entwickler für erforderlich, diese Funktionen virtuell zu gestalten.
Darüber hinaus enthält GObjectClass die virtuellen Funktionen get_property () und set_property (), die neu definiert werden müssen, um solche leistungsstarken Funktionen des GObject-Basistyps und seiner Nachkommen als Eigenschaften in ihren eigenen Objekten zu verwenden. Wir werden im nächsten Artikel darüber sprechen.