GObject: encapsulación, instanciación, introspección

... así como otras palabras de miedo! (c)

Antes de familiarizarnos con algunas características avanzadas del sistema de objetos de tipo GLib, necesitamos hablar sobre una serie de puntos que no mencionamos en los dos artículos anteriores. Esta vez nos familiarizaremos con el tipo básico de GObject, hablemos sobre el hecho de que cualquier descendiente del GObject básico es una doble unidad (y a menudo trino) de objetos de estructura separados, en los que se abren macros misteriosas al comienzo de los archivos de encabezado y archivos con código fuente, utilizando para qué herramientas trabaja el duro RTTI local, por qué GObject y sus descendientes tienen dos destructores (y tres constructores), así como una serie de otras pequeñas cosas interesantes.

imagen

Todo el ciclo sobre GObject:


GObject: lo básico
GObject: herencia e interfaces
GObject: encapsulación, instanciación, introspección

Estructuras Muchas estructuras.


Como sabemos, los descendientes de GObject pueden ser heredados, derivados y no heredados, final. En general, un GObject derivable consiste en una combinación de tres objetos: una estructura de clase, una estructura de instancia y una estructura con datos privados.

Con una estructura de clase, todo es más o menos simple: se describe en el archivo de encabezado y contiene una instancia de la estructura de clase de los padres y punteros de función: "métodos virtuales". Es una buena práctica agregar una pequeña variedad de punteros vacíos al último campo de la estructura para garantizar la compatibilidad ABI. Una instancia de dicha estructura se crea en una instancia cuando se crea la primera instancia de este tipo.

/* animalcat.h */ /*   ,      :) */ typedef struct _AnimalCat AnimalCat; typedef struct _AnimalCatClass AnimalCatClass; typedef struct _AnimalCatPrivate AnimalCatPrivate; struct _AnimalCatClass { GObjectClass parent_class; /*    */ void (*say_meow) (AnimalCat*); /*   */ gpointer padding[10]; /*  ; gpointer -  void* */ }; 

Para los tipos finales, no es necesario definir una estructura de clase.

Se necesita una estructura con datos privados para los objetos derivables. Se define en el archivo de código fuente y se puede acceder a él a través de una función generada automáticamente de la forma animal_cat_get_instance_private (). En este caso, la macro al comienzo del archivo .s debería verse como G_DEFINE_TYPE_WITH_PRIVATE (NamespaceObject, namespace_object, PARENT_TYPE). Puede usar la macro G_DEFINE_TYPE_WITH_CODE (con la macro G_ADD_PRIVATE incluida).

 /* animalcat.c */ #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"; /*    */ } 

Se supone que todos los datos están encapsulados. Para acceder a ellos, puede usar los contenedores habituales: captadores y establecedores, pero, como veremos más adelante, GObject proporciona una herramienta mucho más poderosa para esto: propiedades.

La estructura de instancia, así como la estructura con datos privados, se crea para cada instancia del objeto. De hecho, este es el objeto en sí mismo, con el que trabajará principalmente el usuario final. La estructura se genera automáticamente para los tipos derivables por medio de una macro del archivo de encabezado, por lo que el programador no necesita hacerlo él mismo. Para los tipos finales, debe describirse manualmente en un archivo con código fuente. Como en este caso la estructura no es parte de la interfaz pública del objeto, puede contener datos privados. Obviamente, en este caso no hay necesidad de crear una estructura privada separada.

 /* animaltiger.c */ struct _AnimalTiger { AnimalCat parent; /*         */ int speed; /*   */ }; 

En cuanto a las interfaces, para su implementación es necesario definir solo la estructura de la interfaz, muy similar a la clase habitual. La estructura del objeto de vista _AnimalPredator se generará automáticamente.

 /* animalpredator.h */ typedef struct _AnimalPredatorInterface AnimalPredatorInterface; struct _AnimalPredatorInterface { GTypeInterface parent; /*     GTypeInterface */ void (*hunt) (AnimalPredator* self); /*   */ }; 


Mesa de cuna visual:
imagen

Detección de tipo dinámico en la práctica


En los archivos de encabezado, comenzamos la descripción del nuevo tipo con el uso de dos macros, que, a su vez, se convierten en un conjunto completo de definiciones de macro. En versiones anteriores de GLib, era necesario describir manualmente todo este kit de herramientas. Veamos cuál de estos podemos usar.

ANIMAL_TYPE_CAT: devuelve un identificador entero de tipo GType. Esta macro está estrechamente relacionada con el sistema de tipo GType subyacente a GObject. Definitivamente lo conocerás, lo mencioné solo para que quede claro de dónde viene. Las funciones de la forma animal_cat_get_type () que usa esta definición de macro se generan automáticamente en el archivo fuente al expandir las macros de la familia G_DEFINE_TYPE.

ANIMAL_CAT (obj): se convierte en un puntero a este tipo. Proporciona castas seguras y también realiza comprobaciones de tiempo de ejecución. Como puede ver, el sistema de herencia en GObject generalmente se basa en el hecho de que las estructuras contienen el primer campo como una instancia de la estructura principal y, por lo tanto, de acuerdo con las convenciones de llamada C, el puntero al objeto coincide con el puntero a todos los antepasados ​​de los que se hereda. A pesar de esto, es aconsejable usar la macro provista, en lugar del habitual C-cast. Además, en algunos casos (por ejemplo, cuando se convierte a un tipo de interfaz implementado), una conversión de estilo C no funcionará en absoluto.

ANIMAL_CAT_CLASS (klass): una macro similar para estructuras de clase. La convención prescribe no usar la clase de palabra para compatibilidad con compiladores de C ++.

ANIMAL_IS_CAT (obj): como su nombre lo indica, esta macro determina si obj es un puntero a este tipo (y si es un puntero NULL). Es una buena práctica comenzar los métodos del objeto con tal verificación.

 void animal_cat_run (AnimalCat *self) { assert(ANIMAL_IS_CAT (self)); g_return_if_fail (ANIMAL_IS_CAT (self)); /*   GLib */ /*   */ } 

ANIMAL_IS_CAT_CLASS (klass): lo mismo para las estructuras de clase.

ANIMAL_CAT_GET_CLASS (obj): devuelve un puntero a la estructura de clase correspondiente.

También se genera un conjunto similar de definiciones de macro para las interfaces.

ANIMAL_PREDATOR (obj): conversión al tipo de interfaz.
ANIMAL_IS_PREDATOR (obj): verificación de tipo.
ANIMAL_PREDATOR_GET_IFACE (obj): obteniendo la estructura de la interfaz.

El nombre del objeto se puede obtener utilizando la macro G_OBJECT_TYPE_NAME (obj), que devuelve una cadena si con el nombre del tipo.

Las macros al comienzo del archivo fuente G_DEFINE_TYPE y sus versiones extendidas generan un puntero de la forma animal_cat_parent_class, que devuelve un puntero a la estructura de clases del objeto padre, así como una función de la forma animal_cat_get_instance_private (), si utilizamos la macro correspondiente.

Destructores y otras funciones virtuales.


Como recordamos, al crear cualquier descendiente GObject, se lanzan funciones de la forma animal_cat_init (). Desempeñan el mismo papel que los constructores C ++ y Java. Con los destructores, la situación es más complicada.

La gestión de la memoria en GObject se implementa mediante el recuento de referencias. Cuando se llama a la función g_object_new (), el número de enlaces se establece en uno. En el futuro, podemos aumentar su número con g_object_ref () y disminuir con g_object_unref (). Cuando el número de enlaces se convierte en cero, se iniciará el proceso de destrucción del objeto, que consta de dos fases. Primero, se llama a la función dispose (), que se puede llamar varias veces. Su tarea principal es resolver referencias circulares si es necesario. Después de esto, la función finalize () se llama una vez, en la que se ejecuta todo lo que usualmente se usan para los destructores: se libera memoria, se cierran los descriptores de archivos abiertos, etc.

Un sistema tan complejo fue diseñado para facilitar la creación de carpetas para lenguajes de alto nivel, incluidos aquellos con administración automática de memoria. En la práctica, en el código C, solo se usa finalize (), si el objeto supone la existencia de un destructor.

Las funciones dispose () y finalize (), así como una serie de otras, de las que hablaremos más adelante, son virtuales y están definidas en GObjectClass.

 static void animal_cat_finalize(GObject* obj) { g_print("Buy!\n"); /*  printf()  GLib */ /*    . . */ 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; /*   */ } 

Puede parecer que la última línea de la función animal_cat_finalize () requiere más explicaciones. El puntero animal_cat_parent_class a la clase padre se crea cuando la macro G_DEFINE_TYPE y sus versiones extendidas se expanden. Llamamos a la función correspondiente de la clase padre, que en este caso es directamente una estructura GObjectClass y, a su vez, llama a finalize () de la clase anterior en la cadena. No es necesario preocuparse de que la clase padre no contenga las modificaciones de finalización (); GObject se encargará de esto.

Solo queda recordar que se llama al destructor solo cuando el contador de referencia se pone a cero:

 int main(int argc, char** argv) { AnimalCat* cat = animal_cat_new(); g_object_unref(cat); /*      */ } 

Además de dos destructores, GObjectClass contiene dos constructores virtuales adicionales. constructor () se llama antes del animal_cat_init () ya conocido y crea directamente una instancia de este tipo, built () - after. No es fácil llegar a una situación en la que necesite redefinir estas funciones, a menos que, por supuesto, decida parchear GLib. En la documentación, los desarrolladores dan un ejemplo con la implementación de un singleton, pero en código real nunca he visto tales casos. Sin embargo, para lograr la máxima flexibilidad en todas las etapas del ciclo de vida de la instancia de la instalación, los desarrolladores consideraron necesario hacer que estas funciones sean virtuales.

Además, GObjectClass contiene las funciones virtuales get_property () y set_property (), que deben redefinirse para usar características tan potentes del tipo base GObject y sus descendientes como propiedades en sus propios objetos. Hablaremos de esto en el próximo artículo.

Source: https://habr.com/ru/post/es418443/


All Articles