GObject 学习笔记汇总---11

转自: http://garfileo.is-programmer.com/2011/3/22/gobject-deconstruction.25485.html

GObject 子类对象的析构过程

在“GObject 的信号机制”文中,谈到 GObject 子类对象的析构过程分为两个阶段,第一阶段是 dispose,第二阶段是 finalize。之所以划分成两个阶段而不是一步到位的内存释放,一切皆因尴尬现实之所迫。

引用计数与引用循环

现在,我们都知道了 GObject 类及其子类的对象,其内存管理基于引用计数机制而实现。所谓基于引用计数的内存管理,可大致描述为:

  • 使用 g_object_new 函数进行对象实例化的时候,对象的引用计数为 1;
  • 每次使用 g_object_ref函数引用对象时,对象的引用计数便会增 1;
  • 每次使用 g_object_unref 函数为对象解除引用时,对象的引用计数便会减 1;
  • 在 g_object_unref 函数中,如果发现对象的引用计数为 0,那么则调用对象的析构函数释放对象所占用的资源。

GObjec 类及其子类对象不仅存在继承的关系,还存在互相包含的关系,例如有一个 GObject 子类对象 A 包含(引用)了另一个 GObject 子类对象 B(也就是说对象 B 是对象 A 的属性),而对象 B 有可能反过来又引用了对象 A,这样便构成了引用循环。对于这种情况,对象 A 的析构函数不可能一步到位彻底释放它所占用的资源,可以论证一下:

  • 假设对象 A 的析构函数为 a_deconstruct。
  • a_deconstruct 函数只能在对象 A 的引用计数为 0 的时候被 g_object_unref 函数调用。
  • 在 g_object_new 对象 A 的时候,由于 A 所包含的对象 B 有可能反过来引用 A,那么 A 的引用计数就有可能不是 1 而是 2。
  • 在 g_object_unref 对象 A 的时候,由于此时对象 A 的引用计数可能为 2,所以 g_object_unref 只是将 A 的引用计数减掉 1,而不会调用 a_deconstruct 函数。
  • 对于对象 A 的使用者,在不清楚对象 A 的内部实现时,他会天真的认为 g_object_new 与 g_object_unref 的配对就可以回收 A 的资源,但事实则不然。
  • 对象 A 的使用者如果多次调用 g_object_unref 最终可以引发 a_deconstruct 函数工作,从而实现对象 A 的资源释放,但是使用者并不清楚究竟该运行几次 g_object_unref 才能有这种效果,并且在对象 A 已经被析构的情况下,再次 g_object_unref 对象 A,那么就会出现段错误。

James Henstridge 的方案

为解决引用循环的问题,James Henstridge 给出了一个方案,那就是将 GObject 类及其子类对象的析构过程分为 dispose 阶段与 finalize 阶段。在 dispose 阶段,只解除对象 A 对其所有属性的引用,而在 finalize 阶段释放对象 A 所占用的资源。dispose 阶段可被重复执行多次,而 finalize 阶段仅被执行一次

但是,与其说 James Henstridge 给出了一个方案,不如说他给出了一种约定。因为这种方案在 C 语言中是不可能自动实现,它需要 GObject 库的使用者小心谨慎的保证在 dispose 与 finalize 阶段之间不会出现程序错误(通常是段错误)。

例如“GObject 的信号机制”文中的 MyFile 对象的 dispose 代码:

static void
my_file_dispose (GObject *gobject)
{
        MyFile *self        = MY_FILE (gobject);
        MyFilePrivate *priv = MY_FILE_GET_PRIVATE (self);
        if (priv->file){
                g_io_channel_unref (priv->file);
                priv->file = NULL;
        }
        G_OBJECT_CLASS (my_file_parent_class)->dispose (gobject);
}

MyFile 对象的私有属性 file 是一个指向 GIOChannel 类型变量的指针,它与 GObject 库没有丝毫关系,其内存释放实际上可在 finalize 阶段执行,之所以将其放在 dispose 阶段执行,主要是想揭示 James Henstridge 所给出的约定。这种约定就是必须保证 my_file_dispose 可被无限次的执行而不出错,所以在 my_file_dispose 函数中添加了file 指针有效性判断与野指针消除的代码。

在 GObject 手册中,也有一个 dispose 的示例:

static void
maman_bar_dispose (GObject *gobject)
{
        MamanBar *self = MAMAN_BAR (gobject);
        if (self->priv->an_object)
        {
                g_object_unref (self->priv->an_object);
                self->priv->an_object = NULL;
        }
         
        G_OBJECT_CLASS (maman_bar_parent_class)->dispose (gobject);
}

从上述代码中可看出,MamanBar 对象的私有属性 an_object 是一个 GObject 子类对象,也需要指针有效性判断与野指针消除的代码,因为我们必须要保证对象的 dispose 函数可被多次执行。

那么,多次执行对象的 dispose 函数的任务是由 g_object_unref 来完成吗?

肯定不是。因为 g_object_unref 也不知道该执行多少次 dispose 函数才可以将引用循环打破。

实际上,James Henstridge 所给出的这个约定,并非是让 GObject 自身可以智能的破解引用循环,而是认为 GObject 之外的程序能够分析出引用循环的存在,并由它多次执行对象的 dispose 函数。

例如,基于 GObject 的类型管理与引用计数机制,可在 GObject 库之上建立一个内存回收功能的程序库,GObject 库自身的内存管理机制仅仅是方便上层的内存回收库的实现而已。至于 GObject 库的使用者,依然要像 C 语言程序员那样,兢兢业业的处理好每一块内存的分配与回收。

析构需要向上回溯

在 GObject 子类对象的 dispose 与 finalize 函数中,末尾都有一行比较类似的代码。例如 MyFile 对象的 dispose 函数,有:

G_OBJECT_CLASS (my_file_parent_class)->dispose (gobject);

MyFile 对象的 finalize 函数,有:
G_OBJECT_CLASS (my_file_parent_class)->finalize (gobject);

这两行代码主要是“请求” MyFile 父类对象进行析构。

因为 C 语言不是内建支持面向对象,所以继承需要从上至下的进行结构体包含,那么析构就除了要释放自身资源还需要引发父类对象的析构过程,这样才可以彻底消除整条继承链所占用的资源。

向上回溯析构,这还倒好理解,但是 G_OBJECT_CLASS (my_file_parent_class) 是什么玩意?在 MyFile 类的实现中,我们从未声明与定义过 my_file_parent_class 这个变量或者宏。

不,其实我们既声明了它,也定义了它,其中的玄机就在 my-file.c 源文件开始部分的 G_DEFINE_TYPE 宏之中。如果我们使用命令

gcc -E -P my-file.c > my-file-extend.c

将 my-file.c 中所有的宏进行展开,可以发现:
G_DEFINE_TYPE (MyFile, my_file, G_TYPE_OBJECT);
的展开代码为:
static void my_file_init (MyFile * self);
static void my_file_class_init (MyFileClass * klass);
static gpointer my_file_parent_class = ((void *) 0);
 
static void
my_file_class_intern_init (gpointer klass)
{
    my_file_parent_class = g_type_class_peek_parent (klass);
    my_file_class_init ((MyFileClass *) klass);
} 
 
GType
my_file_get_type (void)
{
    static volatile gsize g_define_type_id__volatile = 0;
    if (g_once_init_enter (&g_define_type_id__volatile)) {
        GType g_define_type_id =
            g_type_register_static_simple (((GType) ((20) << (2))),
                           g_intern_static_string
                           ("MyFile"),
                           sizeof (MyFileClass),
                           (GClassInitFunc)
                           my_file_class_intern_init,
                           sizeof (MyFile),
                           (GInstanceInitFunc)
                           my_file_init,
                           (GTypeFlags) 0); { { {
        };
        }
        }
        g_once_init_leave (&g_define_type_id__volatile,
                   g_define_type_id);
    }
    return g_define_type_id__volatile;
};

可以看出 my_file_parent_class 是一个静态的全局指针,它在 my_file_class_intern_init 函数中指向 MyFile 类的父类结构体。另外,还可以看出 MyFile 类的类结构体初始化函数 my_file_class_init 是由 my_file_class_intern_init 函数调用的,而后者会被 g_object_new 函数调用。

在 my_file_class_init 函数调用之前,将 MyFile 类的父类结构体的地址保存为 my_file_parent_class 指针是有用的。因为我们在 MyFile 类的类结构体初始化函数 my_file_class_init 中覆盖了 MyFile 类所继承的父类结构体的 dispose 与 finaliz 方法:

static
void my_file_class_init (MyFileClass *klass)
{
        g_type_class_add_private (klass, sizeof (MyFilePrivate));
          
        GObjectClass *base_class = G_OBJECT_CLASS (klass);
        ... ... ...
        base_class->dispose      = my_file_dispose;
        base_class->finalize     = my_file_finalize;
        ... ... ...
}

而在 MyFile 对象的 dispose 与 finalize 函数中,我们需要将对象的析构向上回溯到其父类,这时如果直接从 MyFile 类的类结构体中提取父类结构体,那么就会出现 MyFile 对象的 dispose 与 finalize 函数的递归调用。由于预先保存了 MyFile 类的父类结构体地址,那么就可以保证回溯析构的顺利进行。

其实,我们做错了!?

但是 James Henstridge 的约定,不仅仅是要保证 dispose 函数可被多次执行,还要保证在 dispose 函数执行之后并且在 finalize 函数执行之前,程序不会出错。这意味着 dispose 函数不能影响对象的行为(方法)。

无论 MyFIle 类对象的 my_file_dispose 函数还是 GObject 手册中的 maman_bar_dispose 函数的实现,都不符合上述约定。

在 my_file_dispose 函数中,我们不仅释放了 GIOChannel 类型指针 file 所指向的内存区域,还消除了野指针。那么在执行 my_file_dispose 函数之后,显然所有要使用 file 指针的 MyFile 类对象的方法都会出现段错误。同理,maman_bar_dispose 函数也违背了 James Henstridge 的约定。

所以,在 MyFIle 类的设计中 my_file_dispose 函数应当是一个空函数,GObject 手册中的 maman_bar_dispose 函数也应当如此。

对于 GObject 子类对象的哪些属性应当在 dispose 函数中解除引用,哪些属性应当在 finalize 函数中进行资源释放,全面的判定准则就是:既要允许 dispose 函数可重复执行,还要不影响对象的方法。

因此,James Henstridge 的约定跟废话也差不了多少。因为在 dispose 函数中解除任何一个属性的引用都有可能破坏对象的方法!



Table of Contents Introduction I. Concepts Background Data types and programming Exporting a C API The Glib Dynamic Type System Copy functions Conventions Non-Instantiable non-classed fundamental types Instantiable classed types: objects Initialization and Destruction Non-instantiable classed types: Interfaces. Interface Initialization Interface Destruction The GObject base class Object instanciation Object memory management Reference count Weak References Reference counts and cycles Object properties Accessing multiple properties at once The GObject messaging system Closures C Closures non-C closures (for the fearless). Signals Signal registration Signal connection Signal emission The detail argument II. API Reference GType - The GLib Runtime type identification and management system GTypePlugin - An interface for dynamically loadable types GTypeModule - Type loading modules GObject - The base object type Enums and Flags - Enumeration and flags types GBoxed - A mechanism to wrap opaque C structures registered by the type system Generic Values - A polymorphic type that can hold values of any other type Parameters and Values - Standard Parameter and Value Types Varargs Value Collection - Converting varargs to generic values GParamSpec - Metadata for parameter specifications Signals - A means for customization of object behaviour and a general purpose notification mechanism Closures - Functions as first-class objects Value arrays - A container structure to maintain an array of generic values III. Tools Reference glib-mkenums - C language enum description generation utility glib-genmarshal - C code marshaller generation utility for GLib closures gobject-query - display a tree of types IV. Tutorial How To define and implement a new GObject? Boilerplate header code Boilerplate code Object Construction Object Destruction Object methods Non-virtual public methods Virtual public methods Virtual private Methods Chaini
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值