GObject学习(二)

本文是笔者整理网上资料加上一些自己实践的纪录,本文的主体内容转自如下文章:

源博客网址:http://garfileo.is-programmer.com/categories/6934/posts

以下是本文的所主要学习实践的其中c这个小题目的原文链接。
http://garfileo.is-programmer.com/2011/2/28/data-hiden.24848.html

本节主要是体会数据隐藏的意义和方式方法。

  1. 头文件之包含了实例结构体和类结构体的声明,不仅简洁,而且有效对开发者屏蔽了所谓的细节信息,但是更重要的是其他开发者在引用了pm-dlist.h之后,只要该头文件不变,即便具体的实现变化,哪怕是在数据结构中增减变量,都不会引发其他程序的重新编译。当然前提是pm-dlist自己形成了动态链接库。为此笔者做了简单的实验:
$ gcc pm-dlist.c $(pkg-config --cflags --libs gobject-2.0) -fPIC -shared libpm-dlist.so
$ gcc main.c $(pkg-config --cflags --libs gobject-2.0) -L./ -lpm-dlist -o test
$ ./test

这样可以复原原作者的实验结果。唯一区别是,没有将所有文件编译在一起。
然后修改pm-dlist.c,比如在PMDListPrivate中添加变量char label。此时,只需要再次重新编译libpm-dlist.so然后再运行test就可以反映出变化。

  1. 进一步思考的在c++中,private关键字,名义上也是为了实现“数据隐藏”。但是这种隐藏如果明确写在头文件中,那么实际上也没有对别的开发者进行屏蔽。虽然有访问限制,但是,对调用者并非“悄无声息”。特别是,如果出现1所述场景,还会引发问题。针对此,所以就有了所谓“PIMPL idiom”,具体可参考下列链接:
    (1). https://stackoverflow.com/questions/60570/why-should-the-pimpl-idiom-be-used
    (2). https://cpppatterns.com/patterns/pimpl.html

  2. 作者成文于2011年,所以有些代码稍显老旧,除了GObject学习(一)中所说,本文亦有部分,所以说明如下:
    (1). G_TYPE_INSTANCE_GET_PRIVATE已经处于deprecated状态。

G_TYPE_INSTANCE_GET_PRIVATE has been deprecated since version 2.58 and should not be used in newly-written code.Use G_ADD_PRIVATE and the generated your_type_get_instance_private() function instead

从中可以看到,目前官方推荐使用的宏是G_ADD_PRIVATE。

A convenience macro to ease adding private data to instances of a new type in the C section of G_DEFINE_TYPE_WITH_CODE() or G_DEFINE_ABSTRACT_TYPE_WITH_CODE().

笔者采用配合使用的宏就是G_DEFINE_TYPE_WITH_CODE(),注意这个宏是用来定义新“类型”的,其对标的对象是第一节提到的G_DEFINE_TYPE()

G_DEFINE_TYPE_WITH_CODE (PMDList, pm_dlist, G_TYPE_OBJECT, {G_ADD_PRIVATE(PMDList); \
        g_printf("This is in PMDList type add private, %d\n", PMDList_private_offset);});

进一步,就不需要再在_class_init中添加g_type_class_add_private (klass, sizeof (PMDListPrivate));这个函数了。

  1. G_DEFINE_TYPE()和G_DEFINE_TYPE_WITH_CODE()
#define G_DEFINE_TYPE(TN, t_n, T_P)             G_DEFINE_TYPE_EXTENDED (TN, t_n, T_P, 0, {})
#define G_DEFINE_TYPE_EXTENDED(TN, t_n, T_P, _f_, _C_)      _G_DEFINE_TYPE_EXTENDED_BEGIN (TN, t_n, T_P, _f_) {_C_;} _G_DEFINE_TYPE_EXTENDED_END()

#define G_DEFINE_TYPE_WITH_CODE(TN, t_n, T_P, _C_)      _G_DEFINE_TYPE_EXTENDED_BEGIN (TN, t_n, T_P, 0) {_C_;} _G_DEFINE_TYPE_EXTENDED_END()

从中可以看出它们的实现其实是一样的,差别就在于,是否传入了需要特别执行代码块。

#define _G_DEFINE_TYPE_EXTENDED_BEGIN(TypeName, type_name, TYPE_PARENT, flags) \
\
static void     type_name##_init              (TypeName        *self); \
static void     type_name##_class_init        (TypeName##Class *klass); \
static gpointer type_name##_parent_class = NULL; \
static gint     TypeName##_private_offset; \
\
_G_DEFINE_TYPE_EXTENDED_CLASS_INIT(TypeName, type_name) \
\
G_GNUC_UNUSED \
static inline gpointer \
type_name##_get_instance_private (TypeName *self) \
{ \
  return (G_STRUCT_MEMBER_P (self, TypeName##_private_offset)); \
} \
\
GType \
type_name##_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 (TYPE_PARENT, \
                                       g_intern_static_string (#TypeName), \
                                       sizeof (TypeName##Class), \
                                       (GClassInitFunc) type_name##_class_intern_init, \
                                       sizeof (TypeName), \
                                       (GInstanceInitFunc) type_name##_init, \
                                       (GTypeFlags) flags); \
      { /* custom code follows */                        /*G_ADD_PRIVATE()就在这边*/
#define _G_DEFINE_TYPE_EXTENDED_END()   \
        /* following custom code */ \
      }                 \
      g_once_init_leave (&g_define_type_id__volatile, g_define_type_id); \
    }                   \
  return g_define_type_id__volatile;    \
} /* closes type_name##_get_type() */

这里的_G_DEFINE_TYPE_EXTENDED_CLASS_INIT

#define _G_DEFINE_TYPE_EXTENDED_CLASS_INIT(TypeName, type_name) \
static void     type_name##_class_intern_init (gpointer klass) \
{ \
  type_name##_parent_class = g_type_class_peek_parent (klass); \
  if (TypeName##_private_offset != 0) \
    g_type_class_adjust_private_offset (klass, &TypeName##_private_offset); \
  type_name##_class_init ((TypeName##Class*) klass); \
}
  1. 最后需要说明的就是G_ADD_PRIVATE宏,这也是可以在原代码中去掉_add_private()函数调用的原因。
#define G_ADD_PRIVATE(TypeName) { \
  TypeName##_private_offset = \
    g_type_add_instance_private (g_define_type_id, sizeof (TypeName##Private)); \
}

从这段代码中,我们也可以确认,***Private的名字在代码中是不能随便取的。在本例中必须取名PMDListPrivate。简单来说就如代码所示必须是TypeName + “Private”

以下是作者原文转载,放在此处纯属为了自己方便,并防止原文地址丢失。


GObject 子类对象的私有属性模拟

上一篇文章“使用 GObject 库模拟类的数据封装形式”讲述了 GObject 子类化过程,本文以其为基础,进一步讲述如何对数据进行隐藏,即对面向对象程序设计中的“私有属性”概念进行模拟。

非类类型数据的隐藏

第一个问题,可以称之为非类类型数据结构的隐藏,因为 PMDListNode 是普通的 C 结构体。隐藏这个结构体成员的方法有多种。

第一种方法尤为简单,如下:

typedef struct _PMDListNode PMDListNode;
struct  _PMDListNode {
/* private */
        PMDListNode *prev;
        PMDListNode *next;
/* public */
        void *data;
};

只需要向结构体中添加一条注释,用于标明哪些成员是私有的,哪些是可以被直接访问的。

也许你会认为这是开玩笑呢吧!但,这是最符合 C 语言设计理念的做法。C 语言认为,程序员应当知道自己正在干什么,而且保证自己的所作所为是正确的。

倘若你真的这么认为这是在开玩笑,那也没什么。我们还可以使用第二种隐藏的方法,即在 pm-dlist.h 文件中保留下面代码:

typedef struct _PMDListNode PMDListNode;

并将以下代码:

struct  _PMDListNode {
        PMDListNode *prev;
        PMDListNode *next;
        void *data;
};

转移到 pm-dlist.c 文件中。

这下隐藏的非常彻底。当然,这并不能防止用户打开 pm-dlist.c 文件查看 PMDListNode 的定义。不过,我们是自由软件,不怕你看。

如果想半遮半掩,稍微麻烦一些。可以在 pm-dlist.h 中写入以下代码:

typedef struct _PMDListPriv PMDListPriv;
typedef struct _PMDListNode PmdListNode;
struct _PMDListNode {
        PMDListPriv priv;
        void *data;
};

然后,将 PMDListPriv 的定义放在 pm-dlist.c 文件中,如下:

struct _PMDListPriv {
        PMDListNode *prev;
        PMDListNode *next;
};

GObject 子类对象的属性隐藏

GObject 子类对象的属性即继承 GObject 类的类的实例结构体所包含的属性,这句话说起来还真拗口。

考虑一下如何隐藏 PMDList 类的实例结构体中的成员。先回忆一下这个结构体的定义:

typedef struct _PMDList PMDList;
struct  _PMDList {
        GObject parent_instance;
        PMDListNode *head;
        PMDListNode *tail;
};

我们希望 head 与 tail 指针不容他人窥视,虽然可以使用上一节的方式进行数据隐藏,但是 GObject 库为 GObject 子类提供了一种私有结构体的机制,基于它也可以实现数据隐藏,而且更像是隐藏。

首先,我们将 pm-dlist.h 中 PMDList 结构体的定义修改为:

typedef struct _PMDList PMDList;
struct  _PMDList {
        GObject parent_instance;
};

然后,在 pm-dlist.c 文件中,定义一个结构体:

typedef struct _PMDListPrivate PMDListPrivate;
struct  _PMDListPrivate {
        PMDListNode *head;
        PMDListNode *tail;
};

再在 dm-dlist.c 中定义一个宏:

#define PM_DLIST_GET_PRIVATE(obj) (\
        G_TYPE_INSTANCE_GET_PRIVATE ((obj), PM_TYPE_DLIST, PMDListPrivate))

这个宏可以帮助我们从对象中获取所隐藏的私有属性。例如,在 PMDList 类的实例结构体初始化函数中,使用 PM_DLIST_GET_PRIVATE 宏获取 PMDList 对象的 head 与 tail 指针,如下:

static void
pm_dlist_init (PMDList *self)
{
        PMDListPrivate *priv = PM_DLIST_GET_PRIVATE (self);
         
        priv->head = NULL;
        priv->tail = NULL;
}

但是,那个 PMDListPrivate 结构体是怎样被添加到 PMDList 对象中的呢?答案存在于 PMDList 类的类结构体初始化函数之中,如下:

static void
pm_dlist_class_init (PMDListClass *class)
{
        g_type_class_add_private (klass, sizeof (PMDListPrivate));
}

由 于 pm_dlist_class_init 函数会先于 pm_dlist_init 函数被 g_object_new 函数调用,GObject 库的类型管理系统可以从 pm_dlist_class_init 函数中获知 PMDListPrivate 结构体所占用的存储空间,从而 g_object_new 函数在为 PMDList 对象的实例分配存储空间时,便会多分出一部分以容纳 PMDListPrivate 结构体,这样便相当于将一个 PMDListPrivate 结构体挂到 PMDList 对象之中。

GObject 库对私有属性所占用的存储空间是由限制的。一个 GObject 子类对象,它的私有属性及其父类对象的私有属性累加起来不能超过 64 KB。

将一切放到一起

将本文的示例代码综合到一起,便可以得到数据隐藏方法的全貌。

完整的 pm-dlist.h 文件,内容如下:

#ifndef PM_DLIST_H
#define PM_DLIST_H
  
#include <glib-object.h>
  
#define PM_TYPE_DLIST (pm_dlist_get_type ())
  
typedef struct _PMDList PMDList;
struct  _PMDList {
        GObject parent_instance;
};
  
typedef struct _PMDListClass PMDListClass;
struct _PMDListClass {
        GObjectClass parent_class;
};
  
GType pm_dlist_get_type (void);
  
#endif

完整的 pm-dlist.c 文件,内容如下:

#include "pm-dlist.h"
 
G_DEFINE_TYPE (PMDList, pm_dlist, G_TYPE_OBJECT);
#define PM_DLIST_GET_PRIVATE(obj) (\
        G_TYPE_INSTANCE_GET_PRIVATE ((obj), PM_TYPE_DLIST, PMDListPrivate))
 
typedef struct _PMDListNode PMDListNode;
struct  _PMDListNode {
        PMDListNode *prev;
        PMDListNode *next;
        void *data;
};
 
typedef struct _PMDListPrivate PMDListPrivate;
struct  _PMDListPrivate {
        PMDListNode *head;
        PMDListNode *tail;
};
 
static void
pm_dlist_class_init (PMDListClass *klass)
{
        g_type_class_add_private (klass, sizeof (PMDListPrivate));
}
 
static void
pm_dlist_init (PMDList *self)
{
        PMDListPrivate *priv = PM_DLIST_GET_PRIVATE (self);
         
        priv->head = NULL;
        priv->tail = NULL;
}

测试源代码文件 main.c 内容如下:

#include "pm-dlist.h"
  
int
main (void)
{
        /* GObject 库的类型管理系统的初始化 */
        g_type_init ();
     
        PMDList *list;
 
        list = g_object_new (PM_TYPE_DLIST, NULL);
        g_object_unref (list);
          
        return 0;
}

总结

从上述各源文件来看,经过数据隐藏处理之后,pm-dlist.h 被简化,pm-dlist.c 变得更复杂。

事实上,PMDList 类实现完毕后,第三方(即类的使用者)如果要使用这个类,那么他面对的只是 pm-dlist.h 文件,因此一个简洁的 pm-dlist.h 是他最希望看到的。

pm-dlist.c 变得更加复杂,但是这并不是坏事情。因为我们已经尽力将细节隐藏在类的实现部分,而且这部分代码通常也是第三方并不关注的。

这就是数据隐藏的意义。

转载时,希望不要链接文中图片,另外请保留本文原始出处:http://garfileo.is-programmer.com

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值