GObject:封装,实例化,自省

...以及其他令人恐惧的词! (c)

在熟悉GLib类型对象系统的某些高级功能之前,我们需要谈谈前两篇文章中未涉及的许多要点。 这次,我们将熟悉GObject的基本类型,并讨论以下事实:基本GObject的任何后代都是单独的结构对象的双重统一(通常是三位一体),在头文件和带有源代码的文件的开头,神秘的宏会在其中打开,使用苛刻的本地RTTI使用什么工具,为什么GObject及其后代具有两个析构函数(和三个构造函数),以及许多其他有趣的小东西。

图片

关于GObject的整个周期:


GObject:基础知识
GObject:继承和接口
GObject:封装,实例化,自省

结构体 很多结构。


众所周知,GObject的后代可以是继承的-可派生和非继承的-final。 通常,可派生的GObject由三个对象的组合组成:类结构,实例结构和带有私有数据的结构。

对于类结构,一切都或多或少简单-在头文件中进行描述,并包含父类的类结构和函数指针的实例-“虚拟方法”。 优良作法是在结构的最后一个字段中添加一排空指针,以确保ABI兼容性。 创建此类型的第一个实例时,将在一个实例中创建这种结构的实例。

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

对于最终类型,无需定义类结构。

派生对象需要具有私有数据的结构。 它在源代码文件中定义,并且可以通过自动生成的形式为animal_cat_get_instance_private()的函数来访问它。 在这种情况下,.s文件开头的宏应类似于G_DEFINE_TYPE_WITH_PRIVATE(NamespaceObject,namespace_object,PARENT_TYPE)。 您可以使用宏G_DEFINE_TYPE_WITH_CODE(包括宏G_ADD_PRIVATE)。

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

假定所有数据都已封装。 要访问它们,可以使用通常的包装器-getter和setter,但是,正如我们稍后将看到的,GObject为此提供了更强大的工具-属性。

将为对象的每个实例创建实例结构以及带有私有数据的结构。 实际上,这就是最终用户将主要使用的对象本身。 该结构是通过头文件中的宏自动为可派生类型生成的,因此程序员无需自己执行此操作。 对于最终类型,必须在带有源代码的文件中手动进行描述。 由于在这种情况下结构不是对象的公共接口的一部分,因此它可能包含私有数据。 显然,在这种情况下,无需创建单独的私有结构。

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

至于接口,对于它们的实现,仅需要定义接口结构,这与通常的类非常相似。 _AnimalPredator视图对象本身的结构将自动生成。

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


可视婴儿床表:
图片

实践中的动态类型检测


在头文件中,我们使用两个宏开始对新类型的描述,然后将其转换为一组完整的宏定义。 在旧版的GLib中,必须手动描述所有此工具箱。 让我们看看我们可以使用哪些。

ANIMAL_TYPE_CAT:返回GType类型的整数标识符。 此宏与基础GObject的GType类型系统密切相关。 您一定会见到他的,我只提到他是为了清楚地知道他来自哪里。 扩展G_DEFINE_TYPE系列的宏时,会在源文件中自动生成使用此宏定义的形式animal_cat_get_type()的函数。

ANIMAL_CAT(obj):转换为此类型的指针。 提供安全的等级,并执行运行时检查。 如您所见,GObject中的继承系统通常基于以下事实:结构包含第一个字段作为父结构的实例,因此,根据C调用约定,指向对象的指针与指向从其继承的所有祖先的指针重合。 尽管如此,建议使用提供的宏,而不是通常的C-cast。 另外,在某些情况下(例如,当转换为已实现的接口类型时),C样式的转换完全不起作用。

ANIMAL_CAT_CLASS(类别):类结构的类似宏。 该约定规定不要使用单词类来与C ++编译器兼容。

ANIMAL_IS_CAT(obj):顾名思义,此宏确定obj是否是指向该类型的指针(以及它是否为NULL指针)。 最好通过这样的检查来启动对象的方法。

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

ANIMAL_IS_CAT_CLASS(类别):类结构相同。

ANIMAL_CAT_GET_CLASS(obj):返回一个指向相应类结构的指针。

还为接口生成了一组相似的宏定义。

ANIMAL_PREDATOR(obj):转换为接口类型。
ANIMAL_IS_PREDATOR(obj):类型检查。
ANIMAL_PREDATOR_GET_IFACE(obj):获取接口结构。

可以使用宏G_OBJECT_TYPE_NAME(obj)获得对象名称,该宏返回具有类型名称的si字符串。

源文件G_DEFINE_TYPE及其扩展版本开头的宏会生成格式为animal_cat_parent_class的指针,如果使用相应的宏,则该指针将返回指向父对象的类结构的指针,以及格式为animal_cat_get_instance_private()的函数。

析构函数和其他虚拟函数


我们记得,在创建任何GObject后代时,将启动animal_cat_init()形式的函数。 它们执行与C ++和Java构造函数相同的角色。 使用析构函数,情况会更加复杂。

GObject中的内存管理是使用引用计数实现的。 调用g_object_new()函数时,链接数设置为1。 将来,我们可以通过g_object_ref()增加它们的数量,并通过g_object_unref()减少它们的数量。 当链接数变为零时,将启动由两个阶段组成的对象破坏过程。 首先,调用dispose()函数,该函数可以多次调用。 它的主要任务是在必要时解决循环引用。 之后,一次调用finalize()函数,在其中执行析构函数通常用于的所有操作-释放内存,关闭打开的文件描述符,依此类推。

这样一个复杂的系统旨在促进高级语言(包括具有自动内存管理功能的语言)的活页夹的创建。 实际上,在C代码中,如果对象假定存在析构函数,则通常仅使用finalize()。

函数dispose()和finalize()以及我们稍后将要讨论的许多其他函数都是虚函数,并且在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; /*   */ } 

animal_cat_finalize()函数的最后一行似乎需要进一步说明。 当宏G_DEFINE_TYPE及其扩展版本被扩展时,将创建指向父类的animal_cat_parent_class指针。 我们从父类中调用相应的函数,在这种情况下,该父类直接是GObjectClass结构,然后依次调用链中上一类的finalize()。 不必担心父类可能不包含finalize()重写; GObject将处理此问题。

只需回顾一下,只有在参考计数器为零时才调用析构函数:

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

除了两个析构函数外,GObjectClass还包含两个附加的虚拟构造函数。 在已知的animal_cat_init()之前调用constructor(),并在之后直接创建此类的实例Constructed()。 需要重新定义这些功能的情况并不容易,除非您当然决定修补GLib本身。 在文档中,开发人员给出了一个单例实现的示例,但是在实际代码中,我从未见过这种情况。 但是,为了在设施实例生命周期的所有阶段实现最大的灵活性,开发人员认为有必要将这些功能虚拟化。

此外,GObjectClass包含虚函数get_property()和set_property(),必须对其进行重新定义,以使用基本GObject类型及其后代的强大功能作为其自己对象中的属性。 我们将在下一篇文章中讨论。

Source: https://habr.com/ru/post/zh-CN418443/


All Articles