... ainsi que d'autres mots effrayants! (c)
Avant de nous familiariser avec certaines fonctionnalités avancées du système d'objets de type GLib, nous devons parler d'un certain nombre de points que nous n'avons pas abordés dans les deux articles précédents. Cette fois, nous allons nous familiariser avec le type de base de GObject, parler du fait que tout descendant du GObject de base est une double unité (et souvent trinitaire) d'objets de structure séparés, dans lesquels de mystérieuses macros sont ouvertes au début des fichiers d'en-tête et des fichiers avec le code source, en utilisant pour quels outils fonctionne la dure RTTI locale, pourquoi GObject et ses descendants ont-ils deux destructeurs (et trois constructeurs), ainsi qu'un certain nombre d'autres petites choses intéressantes.

Tout le cycle sur GObject:
GObject: les basesGObject: héritage et interfacesGObject: encapsulation, instanciation, introspection
Structures Beaucoup de structures.
Comme nous le savons, les descendants de GObject peuvent être hérités - dérivables et non hérités - final. En général, un GObject dérivable se compose d'une combinaison de trois objets: une structure de classe, une structure d'instance et une structure avec des données privées.
Avec une structure de classe, tout est plus ou moins simple - il est décrit dans le fichier d'en-tête et contient une instance de la structure de classe du parent et des pointeurs de fonction - «méthodes virtuelles». Il est recommandé d'ajouter un petit tableau de pointeurs vides au dernier champ de la structure pour garantir la compatibilité ABI. Une instance d'une telle structure est créée en une seule instance lors de la création de la première instance de ce type.
typedef struct _AnimalCat AnimalCat; typedef struct _AnimalCatClass AnimalCatClass; typedef struct _AnimalCatPrivate AnimalCatPrivate; struct _AnimalCatClass { GObjectClass parent_class; void (*say_meow) (AnimalCat*); gpointer padding[10]; };
Pour les types finaux, il n'est pas nécessaire de définir une structure de classe.
Une structure avec des données privées est nécessaire pour les objets dérivables. Il est défini dans le fichier de code source, et l'accès à celui-ci peut être obtenu via une fonction générée automatiquement sous la forme animal_cat_get_instance_private (). Dans ce cas, la macro au début du fichier .s doit ressembler à G_DEFINE_TYPE_WITH_PRIVATE (NamespaceObject, namespace_object, PARENT_TYPE). Vous pouvez utiliser la macro G_DEFINE_TYPE_WITH_CODE (avec la macro G_ADD_PRIVATE incluse).
#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"; /* */ }
Toutes les données sont supposées être encapsulées. Pour y accéder, vous pouvez utiliser les wrappers habituels - getters et setters, mais, comme nous le verrons plus loin, GObject fournit un outil beaucoup plus puissant pour cela - les propriétés.
La structure d'instance, ainsi que la structure avec des données privées, est créée pour chaque instance de l'objet. Il s'agit en fait de l'objet lui-même, avec lequel l'utilisateur final travaillera principalement. La structure est automatiquement générée pour les types dérivables au moyen d'une macro du fichier d'en-tête, donc le programmeur n'a pas besoin de le faire lui-même. Pour les types finaux, il doit être décrit manuellement dans un fichier avec le code source. Étant donné que dans ce cas, la structure ne fait pas partie de l'interface publique de l'objet, elle peut contenir des données privées. De toute évidence, dans ce cas, il n'est pas nécessaire de créer une structure privée distincte.
struct _AnimalTiger { AnimalCat parent; int speed; };
Quant aux interfaces, pour leur implémentation il est nécessaire de définir uniquement la structure d'interface, très similaire à la classe habituelle. La structure de l'objet de vue _AnimalPredator lui-même sera générée automatiquement.
typedef struct _AnimalPredatorInterface AnimalPredatorInterface; struct _AnimalPredatorInterface { GTypeInterface parent; void (*hunt) (AnimalPredator* self); };
Table de lit visuel:

Détection dynamique de type en pratique
Dans les fichiers d'en-tête, nous avons commencé la description du nouveau type avec l'utilisation de deux macros, qui, à leur tour, sont converties en un ensemble complet de définitions de macro. Dans les anciennes versions de GLib, il était nécessaire de décrire manuellement l'ensemble de cette boîte à outils. Voyons lesquels nous pouvons utiliser.
ANIMAL_TYPE_CAT: retourne un identifiant entier de type GType. Cette macro est étroitement liée au système de type GType sous-jacent à GObject. Vous le rencontrerez certainement, je ne l'ai mentionné que pour qu'il soit clair d'où il vient. Les fonctions de la forme animal_cat_get_type () qui utilisent cette définition de macro sont générées automatiquement dans le fichier source lors du développement des macros de la famille G_DEFINE_TYPE.
ANIMAL_CAT (obj): transtypé en un pointeur vers ce type. Fournit des castes sûres et effectue également des vérifications d'exécution. Comme vous pouvez le voir, le système d'héritage dans GObject est généralement basé sur le fait que les structures contiennent le premier champ en tant qu'instance de la structure parent et, par conséquent, selon les conventions d'appel C, le pointeur vers l'objet coïncide avec le pointeur vers tous les ancêtres dont il est hérité. Malgré cela, il est conseillé d'utiliser la macro fournie, plutôt que le C-cast habituel. De plus, dans certains cas (par exemple, lors de la conversion vers un type d'interface implémenté), une conversion de style C ne fonctionnera pas du tout.
ANIMAL_CAT_CLASS (klass): une macro similaire pour les structures de classe. La convention prescrit de ne pas utiliser la classe word pour la compatibilité avec les compilateurs C ++.
ANIMAL_IS_CAT (obj): comme son nom l'indique, cette macro détermine si obj est un pointeur vers ce type (et s'il s'agit d'un pointeur NULL). Il est recommandé de démarrer les méthodes de l'objet avec une telle vérification.
void animal_cat_run (AnimalCat *self) { assert(ANIMAL_IS_CAT (self)); g_return_if_fail (ANIMAL_IS_CAT (self)); }
ANIMAL_IS_CAT_CLASS (klass): idem pour les structures de classe.
ANIMAL_CAT_GET_CLASS (obj): retourne un pointeur sur la structure de classe correspondante.
Un ensemble similaire de définitions de macro est également généré pour les interfaces.
ANIMAL_PREDATOR (obj): transtypé en type d'interface.
ANIMAL_IS_PREDATOR (obj): vérification de type.
ANIMAL_PREDATOR_GET_IFACE (obj): obtention de la structure d'interface.
Le nom de l'objet peut être obtenu à l'aide de la macro G_OBJECT_TYPE_NAME (obj), qui renvoie une chaîne si avec le nom du type.
Les macros au début du fichier source G_DEFINE_TYPE et ses versions étendues génèrent un pointeur de la forme animal_cat_parent_class, qui renvoie un pointeur vers la structure de classe de l'objet parent, ainsi qu'une fonction de la forme animal_cat_get_instance_private (), si nous avons utilisé la macro correspondante.
Destructeurs et autres fonctions virtuelles
Comme nous nous en souvenons, lors de la création d'un descendant de GObject, les fonctions de la forme animal_cat_init () sont lancées. Ils jouent le même rôle que les constructeurs C ++ et Java. Avec les destructeurs, la situation est plus compliquée.
La gestion de la mémoire dans GObject est implémentée à l'aide du comptage de références. Lorsque la fonction g_object_new () est appelée, le nombre de liens est défini sur un. À l'avenir, nous pouvons augmenter leur nombre avec g_object_ref () et diminuer avec g_object_unref (). Lorsque le nombre de liens devient nul, le processus de destruction de l'objet, composé de deux phases, sera lancé. Tout d'abord, la fonction dispose () est appelée, qui peut être appelée plusieurs fois. Sa tâche principale est de résoudre les références circulaires si nécessaire. Après cela, la fonction finalize () est appelée une fois, dans laquelle tout ce pour quoi les destructeurs sont généralement utilisés est exécuté - la mémoire est libérée, les descripteurs de fichiers ouverts sont fermés, etc.
Un système aussi complexe a été conçu pour faciliter la création de classeurs pour les langages de haut niveau, y compris ceux avec gestion automatique de la mémoire. En pratique, dans le code C, seul finalize () est généralement utilisé, si l'objet suppose l'existence d'un destructeur.
Les fonctions dispose () et finalize (), ainsi qu'un certain nombre d'autres, dont nous parlerons plus loin, sont virtuelles et sont définies dans 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; }
La dernière ligne de la fonction animal_cat_finalize () peut sembler nécessiter des explications supplémentaires. Le pointeur animal_cat_parent_class vers la classe parente est créé lorsque la macro G_DEFINE_TYPE et ses versions étendues sont développées. Nous appelons la fonction correspondante de la classe parente, qui dans ce cas est directement une structure GObjectClass, et elle, à son tour, appelle finalize () de la classe précédente de la chaîne. Il n'y a pas lieu de s'inquiéter que la classe parente ne contienne pas de substitutions de finalize (); GObject s'en occupera.
Il ne reste plus qu'à rappeler que le destructeur n'est appelé que lorsque le compteur de référence est remis à zéro:
int main(int argc, char** argv) { AnimalCat* cat = animal_cat_new(); g_object_unref(cat); }
En plus de deux destructeurs, GObjectClass contient deux constructeurs virtuels supplémentaires. constructor () est appelé avant l'animal_cat_init () déjà connu et crée directement une instance de ce type, built () - after. Ce n'est pas facile de trouver une situation dans laquelle vous devez redéfinir ces fonctions, à moins bien sûr que vous ne décidiez de patcher GLib lui-même. Dans la documentation, les développeurs donnent un exemple avec l'implémentation d'un singleton, mais en vrai code je n'ai jamais vu de tels cas. Cependant, pour obtenir une flexibilité maximale à toutes les étapes du cycle de vie de l'instance d'installation, les développeurs ont estimé nécessaire de rendre ces fonctions virtuelles.
De plus, GObjectClass contient les fonctions virtuelles get_property () et set_property (), qui doivent être redéfinies pour utiliser des fonctionnalités puissantes du type de base GObject et ses descendants en tant que propriétés dans leurs propres objets. Nous en parlerons dans le prochain article.