GObject: encapsulamento, instanciação, introspecção

... bem como outras palavras assustadoras! c)

Antes de nos familiarizarmos com alguns recursos avançados do sistema de objetos do tipo GLib, precisamos falar sobre vários pontos que não abordamos nos dois artigos anteriores. Desta vez, vamos nos familiarizar com o tipo básico de GObject, falar sobre o fato de que qualquer descendente do GObject básico é uma unidade dupla (e geralmente trinitária) de objetos de estrutura separados, nos quais macros misteriosas são abertas no início dos arquivos e arquivos de cabeçalho com código-fonte, usando Para quais ferramentas o RTTI local rígido trabalha, por que o GObject e seus descendentes têm dois destruidores (e três construtores), além de várias outras pequenas coisas interessantes.

imagem

Todo o ciclo sobre o GObject:


GObject: o básico
GObject: herança e interfaces
GObject: encapsulamento, instanciação, introspecção

Estruturas Muitas estruturas.


Como sabemos, os descendentes de GObject podem ser herdados - derivados e não herdados - final. Em geral, um GObject derivável consiste em uma combinação de três objetos: uma estrutura de classe, uma estrutura de instância e uma estrutura com dados particulares.

Com uma estrutura de classes, tudo é mais ou menos simples - é descrito no arquivo de cabeçalho e contém uma instância da estrutura de classes e indicadores de função dos pais - “métodos virtuais”. É uma boa prática adicionar uma pequena variedade de ponteiros nulos ao último campo da estrutura para garantir a compatibilidade com ABI. Uma instância dessa estrutura é criada em uma instância ao criar a primeira instância deste 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 tipos finais, não há necessidade de definir uma estrutura de classe.

Uma estrutura com dados privados é necessária para objetos deriváveis. Ele é definido no arquivo de código-fonte e o acesso a ele pode ser obtido através de uma função gerada automaticamente no formato animal_cat_get_instance_private (). Nesse caso, a macro no início do arquivo .s deve se parecer com G_DEFINE_TYPE_WITH_PRIVATE (NamespaceObject, namespace_object, PARENT_TYPE). Você pode usar a macro G_DEFINE_TYPE_WITH_CODE (com a macro G_ADD_PRIVATE incluída).

 /* 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"; /*    */ } 

Todos os dados são assumidos como encapsulados. Para acessá-los, você pode usar os invólucros usuais - getters e setters, mas, como veremos mais adiante, o GObject fornece uma ferramenta muito mais poderosa para essas + propriedades.

A estrutura da instância, bem como a estrutura com dados particulares, é criada para cada instância do objeto. Na verdade, esse é o objeto em si, com o qual o usuário final trabalhará principalmente. A estrutura é gerada automaticamente para tipos deriváveis ​​por meio de uma macro do arquivo de cabeçalho, portanto, o programador não precisa fazer isso sozinho. Para tipos finais, ele deve ser descrito manualmente em um arquivo com código fonte. Como nesse caso a estrutura não faz parte da interface pública do objeto, ela pode conter dados privados. Obviamente, nesse caso, não há necessidade de criar uma estrutura privada separada.

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

Quanto às interfaces, para sua implementação é necessário definir apenas a estrutura da interface, muito semelhante à classe usual. A estrutura do objeto de exibição _AnimalPredator em si será gerada automaticamente.

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


Tabela visual do berço:
imagem

Detecção dinâmica de tipo na prática


Nos arquivos de cabeçalho, iniciamos a descrição do novo tipo com o uso de duas macros, que, por sua vez, são convertidas em um conjunto inteiro de definições de macro. Nas versões mais antigas do GLib, era necessário descrever manualmente todo este kit de ferramentas. Vamos ver qual desses podemos usar.

ANIMAL_TYPE_CAT: retorna um identificador inteiro do tipo GType. Essa macro está intimamente relacionada ao sistema do tipo GType subjacente ao GObject. Você definitivamente o conhecerá, eu o mencionei apenas para que fique claro de onde ele vem. Funções do formulário animal_cat_get_type () que usa essa definição de macro são geradas automaticamente no arquivo de origem ao expandir macros da família G_DEFINE_TYPE.

ANIMAL_CAT (obj): convertido em um ponteiro para esse tipo. Fornece castas seguras e também executa verificações de tempo de execução. Como você pode ver, o sistema de herança no GObject geralmente é baseado no fato de que as estruturas contêm o primeiro campo como uma instância da estrutura pai e, portanto, de acordo com as convenções de chamada C, o ponteiro para o objeto coincide com o ponteiro para todos os ancestrais dos quais ele é herdado. Apesar disso, é aconselhável usar a macro fornecida, em vez do C-cast usual. Além disso, em alguns casos (por exemplo, ao transmitir para um tipo de interface implementado), uma conversão no estilo C não funcionará.

ANIMAL_CAT_CLASS (klass): uma macro semelhante para estruturas de classe. A convenção prescreve não usar a classe de palavras para compatibilidade com compiladores C ++.

ANIMAL_IS_CAT (obj): como o nome indica, essa macro determina se obj é um ponteiro para esse tipo (e se é um ponteiro NULL). É uma boa prática iniciar os métodos do objeto com essa verificação.

 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): o mesmo para estruturas de classe.

ANIMAL_CAT_GET_CLASS (obj): retorna um ponteiro para a estrutura de classe correspondente.

Um conjunto semelhante de definições de macro também é gerado para interfaces.

ANIMAL_PREDATOR (obj): convertido para o tipo de interface.
ANIMAL_IS_PREDATOR (obj): verificação de tipo.
ANIMAL_PREDATOR_GET_IFACE (obj): obtendo a estrutura da interface.

O nome do objeto pode ser obtido usando a macro G_OBJECT_TYPE_NAME (obj), que retorna uma string si com o nome do tipo.

As macros no início do arquivo de origem G_DEFINE_TYPE e suas versões estendidas geram um ponteiro do formulário animal_cat_parent_class, que retorna um ponteiro para a estrutura de classe do objeto pai, bem como uma função do formulário animal_cat_get_instance_private (), se usamos a macro correspondente.

Destruidores e outras funções virtuais


Como lembramos, ao criar qualquer descendente GObject, são executadas funções no formato animal_cat_init (). Eles desempenham a mesma função que os construtores C ++ e Java. Com os destruidores, a situação é mais complicada.

O gerenciamento de memória no GObject é implementado usando a contagem de referência. Quando a função g_object_new () é chamada, o número de links é definido como um. No futuro, podemos aumentar seu número com g_object_ref () e diminuir com g_object_unref (). Quando o número de links se tornar zero, o processo de destruição do objeto, composto por duas fases, será iniciado. Primeiro, a função dispose () é chamada, que pode ser chamada várias vezes. Sua principal tarefa é resolver referências circulares, se necessário. Depois disso, a função finalize () é chamada uma vez, na qual tudo o que os destruidores costumam ser usados ​​é executado - a memória é liberada, os descritores de arquivos abertos são fechados e assim por diante.

Um sistema tão complexo foi projetado para facilitar a criação de binders para linguagens de alto nível, incluindo aquelas com gerenciamento automático de memória. Na prática, no código C, apenas finalize () é normalmente usado, se o objeto assume a existência de um destruidor.

As funções dispose () e finalize (), assim como várias outras, sobre as quais falaremos mais adiante, são virtuais e são definidas no 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; /*   */ } 

A última linha da função animal_cat_finalize () pode parecer exigir mais explicações. O ponteiro animal_cat_parent_class para a classe pai é criado quando a macro G_DEFINE_TYPE e suas versões estendidas são expandidas. Chamamos a função correspondente da classe pai, que neste caso é diretamente uma estrutura GObjectClass e, por sua vez, chama finalize () da classe anterior na cadeia. Não há necessidade de se preocupar que a classe pai possa não conter substituições finalize (); o GObject cuidará disso.

Resta apenas lembrar que o destruidor é chamado apenas quando o contador de referência é zerado:

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

Além de dois destruidores, o GObjectClass contém dois construtores virtuais adicionais. O constructor () é chamado antes do animal_cat_init () já conhecido e cria diretamente uma instância desse tipo, construct () - after. Não é fácil criar uma situação em que você precise redefinir essas funções, a menos que decida corrigir o próprio GLib. Na documentação, os desenvolvedores dão um exemplo com a implementação de um singleton, mas em código real eu nunca vi esses casos. No entanto, para obter a máxima flexibilidade em todas as fases do ciclo de vida da instância da instalação, os desenvolvedores consideraram necessário tornar essas funções virtuais.

Além disso, GObjectClass contém as funções virtuais get_property () e set_property (), que devem ser redefinidas para usar recursos avançados do tipo base GObject e seus descendentes como propriedades em seus próprios objetos. Falaremos sobre isso no próximo artigo.

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


All Articles