转载请注明出处 blog.csdn.net/pingf0 或www.cnblogs.com/pingf
PART 9
注:这一部分的大部分内容源自google wallpaper上对gtk mail archive上关于gobject的一些翻译,这里只是引用了下【链接见前文】,因为原文对此部分的描述还是相当不错的,至少比我觉的自己要写还写不了这么好。
属性
JAVA,C#好像有着这么一个概念【从语言层面是原生的】,但是标准的C++还是没有的【微软的C++/CLI实现了属性,但微软的并不是标准的,另外大名鼎鼎的QT也实现了属性这一概念】
属性到底是什么,个人感觉跟成员变量差不了多少,无非是我们可以明确的声明他们的可读可写。。。。另外如果把它当成成员变量,那么访问时可以像成员变量一样容易,但实际底层却调用专门的函式来对其访问。
个人感觉:如果程序员对自己的程序都是明了的,属性这个东西根本就没必要!【哎,又大放厥词啦。。。。。。Orz】从使用的角度而言,带来的方便并不是很大,但从代码的角度却要增加不小的开销。
用C++我们可以使用模板配合宏来模拟属性,并且这种模拟在声明的时候麻烦了些。但如果用C来实现,仅靠函式指针和宏就有些力不从心了,至少一步一步实现起来会相当麻烦。GObject对于属性的实现还是比较不错的,使用起来也还算方便,这得益于实现了泛型和信号的机制。
下面要转载一部分文字,因为感觉对这一块的描述相当的不错【不过是翻译的】
“
GValues
C是一门强类型语言,也就是说变量声明的类型必须和它被使用的方式保持一致,否则编译器就会报错。这是一件好事,它使得程序编写起来更迅速,帮助我们发现可能会导致系统崩溃或者不安全的因素。但这又是件坏事,因为实际上程序员活在一个很难什么事都保持严格的世界上,而且我们也希望声明的类型能够具备多态的能力 - 也就是说类型能够根据上下文来改变它们自己的特性。通过C语言的转型我们可以获得一些多态的能力,如上面所讨论过的继承。然而,当使用无类型指针作为参数传递给函数时,可能问题会比较多。幸运的是,类型系统给了我们另外一个C语言没有的工具:GType。
让我们更清楚的描述一下问题吧。我需要一种数据类型,可以实现一个可以容纳多类型元素的链表,我想为这个链表编写一些接口,可以不依赖于任何特定的类型,并且不需要我为每种数据类型声明一个多余的函数。这种接口必然能涵盖多种类型,所以我们称它为GValue(Generic Value,泛型)。该如何实现这样一个类型呢?
我们创建了封装这种类型的结构体,它具有两个成员域:所有基础类型的联合(union),和表示保存在这个union中的值的GType。这样我们就可以将值的类型隐藏在GValue中,并且通过检查对GValue的操作来保证类型是安全的。这样还减少了多余的以类型为基础的操作接口(如get_int,set_float,...),统一换成了g_value_*的形式。
细心的读者会发现每个GValue都占据了最大的基础类型的内存大小(通常是8字节),再加上GType自己的大小。是的,GValues在空间上不是最优的,包含了不小的浪费,因此不应该被大量的使用它。它最常被用在定义一些泛型的API上。
属性是如何工作的这一点稍稍超出了我们要讨论的范围,但是这对于理解属性本身还是很有帮助的。
/* 让我们使用GValue来复制整型数据! */
#define g_value_new(type) g_value_init (g_new (GValue, 1), type)
GValue *a = g_value_new (G_TYPE_UCHAR);
GValue *b = g_value_new (G_TYPE_INT);
int c = 0;
g_value_set_uchar (a, 'a');
g_value_copy (a, b);
c = g_value_get (b);
g_print ("w00t: %d\n", c);
g_free (a);
g_free (b);
设计
我们已经在上面接触过属性了,对它们有了初步的认识,现在我们将继续来了解一下设计它们的最初动机。 要编写一个泛型的属性设置机制,我们需要一个将其参数化的方法,以及与实例结构体中的成员变量名查重的机制。从外部上看,我们希望使用C字符串来区分属性和公有API,但是内部上来说,这样做会严重的影响效率。因此我们枚举化了属性,使用索引来标识它们。
上面提过属性规格,在Glib中被称作!GParamSpec,它保存了对象的gtype,对象的属性名称,属性枚举ID,属性默认值,边界值等,类型系统用!GParamSpec来将属性的字符串名转换为枚举的属性ID,GParamSpec也是一个能把所有东西都粘在一起的大胶水。
当我们需要设置或者获取一个属性的值时,传入属性的名字,并且带上GValue用来保存我们要设置的值,调用g_object_set/get_property。g_object_set_property函数将在GParamSpec中查找我们要设置的属性名称,查找我们对象的类,并且调用对象的set_property方法。这意味着如果我们要增加一个新的属性,就必须要覆盖默认的set/get_property方法。而且基类包含的属性将被它自己的set/get_property方法所正常处理,因为!GParamSpec就是从基类传递下来的。最后,应该记住,我们必须事先通过对象的class_init方法来传入GParamSpec参数,用于安装上属性!
假设我们已经有了如上一节所描述的那样一个可用的框架,那么现在让我们来为SomeObject加入处理属性的代码吧!
代码(头文件)
a. 除了我们增加了两个属性外,其余同上面的一样。
/* “实例结构体”定义所有的数据域,实例对象将是唯一的 */
typedef struct _SomeObject SomeObject;
struct _SomeObject
{
GObject parent_obj;
/* 新增加的属性 */ //ß其实还是成员变量,属性机制是额外提供的
int a;
float b;
/* 下面是一些数据 */
};
代码(源文件)
a. 创建一个枚举类型用来内部记录属性。
enum
{
OBJECT_PROPERTY_A = 1 << 1;
OBJECT_PROPERTY_B = 1 << 2;
};
b. 实现新增的处理属性的函数。
void some_object_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
{
SomeObject *self = SOME_OBJECT (object);
switch (property_id)
{
case OBJECT_PROPERTY_A:
g_value_set_int (value, self-> a);
break;
case OBJECT_PROPERTY_B:
g_value_set_float (value, self-> b);
break;
default: /* 没有属性用到这个ID!! */
}
}
void some_object_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
{
SomeObject *self = SOME_OBJECT (object);
switch (property_id)
{
case OBJECT_PROPERTY_A:
self-> a = g_value_get_int (value);
break;
case OBJECT_PROPERTY_B:
self-> b = g_value_get_float (value);
break;
default: /* 没有属性用到这个ID!! */
}
}
c. 覆盖继承自基类的set/get_property方法,并且传入GParamSpecs。
/* 这里是我们覆盖函数的地方 */
void some_object_class_init (gpointer g_class, gpointer class_data)
{
GObjectClass *this_class = G_OBJECT_CLASS (g_class);
GParamSpec *spec;
this_class-> constructor = &some_object_constructor;
this_class-> dispose = &some_object_dispose;
this_class-> finalize = &some_object_finalize;
this_class-> set_property = &some_object_set_property;
this_class-> get_property = &some_object_get_property;
spec = g_param_spec_int
(
"property-a", /* 属性名称 */
"a", /* 属性昵称 */
"Mysterty value 1", /* 属性描述 */
5, /* 属性最大值 */
10, /* 属性最小值 */
5, /* 属性默认值 */
G_PARAM_READABLE |G_PARAM_WRITABLE /* GParamSpecFlags */
);
g_object_class_install_property (this_class, OBJECT_PROPERTY_A, spec);
spec = g_param_spec_float
(
"property-b", /* 属性名称 */
"b", /* 属性昵称 */
"Mysterty value 2" /* 属性描述 */
0.0, /* 属性最大值 */
1.0, /* 属性最小值 */
0.5, /* 属性默认值 */
G_PARAM_READABLE |G_PARAM_WRITABLE /* GParamSpecFlags */
);
g_object_class_install_property (this_class, OBJECT_PROPERTY_B, spec);
}
”
最后再啰嗦两句
1.我们设置属性,就会调用指定的set函式去设置相应的成员变量,获取属性时则调用get函式将对应的成员变量当前的值返回。
而成员变量本身的修改和获得,跟GObject属性机制本身是不想干的。
2.对于继承下来的属性,是遵循从底至顶的原则的。
3.如果对属性的设置不在合理范围之内,将返回一个错误。
4.接口里面也可以定义属性,通常在base_init里面实现。
5,另外,我们可以通过g_object_set和g_object_get来获取和设置一个或多个属性。