GObject学习笔记(三)可派生抽象类型

paw5zx Lv4

可派生类型

根据继承性,GObject中可分为两种类型:最终类型(final type)和可派生类型(derivable type)。最终类型没有任何子对象,而可派生类型可以有子对象。

这两种对象的主要区别在于它们的类:

  • 最终类型没有自己的类区域,即其类结构体中唯一成员是其父类的类结构体。
  • 可派生类型的对象则拥有自己的类区域,在这个类区域中可以定义自己特有的函数、属性等,这使得可派生类型的类可以被进一步继承和扩展。其类结构体对子类是开放的。

G_DECLARE_DERIVABLE_TYPE可以用来在头文件中声明一个可派生类型。

抽象类型

抽象类型没有实例。此类型是可派生的,其子类可以使用抽象类型的函数和信号。

本节将使用三个类型PawNumberPawIntPawDouble

PawDouble在前一节中已定义,PawInt的定义类似PawDoublePawIntPawDouble分别表示整数和浮点数。

PawNumber代表数字,数字是比整数和浮点数更抽象的概念。所以PawNumber被设计为PawIntPawDouble的父类,并且它不可实例化,因为他是抽象类型。

PawIntPawDouble有三个操作:乘法、除法、一元取反操作和(为了方便展示,省去加法和减法),除此以外还会定义一个to_s操作,它可以将PawNumber代表的值转化为字符串。这些操作可以在PawNumber类型上定义。

上述代码都会在后文给出

G_BEGIN_DECLS和G_END_DECLS

G_BEGIN_DECLSG_END_DECLS是GLib提供的宏,用于在C++环境中保持C语言链接约定。这两个宏主要用于确保当GObject库的头文件被C++编译器包含时,内部声明的C函数不会被当作C++函数进行名称修饰,从而可以在C++代码中正常调用C库函数。

等价于:

1
2
3
4
5
6
7
#ifdef __cplusplus
#define G_BEGIN_DECLS extern "C" {
#define G_END_DECLS }
#else
#define G_BEGIN_DECLS
#define G_END_DECLS
#endif

G_DECLARE_DERIVABLE_TYPE

1
#define G_DECLARE_DERIVABLE_TYPE(ModuleObjName, module_obj_name, MODULE, OBJ_NAME, ParentName)

其中:

  • ModuleObjName:新类型的名称,驼峰形式(如PawDouble
  • module_obj_name:新类型名称,全小写,单词之间使用下划线分隔(如paw_double
  • MODULE:模块的名称,全大写(如PAW
  • OBJ_NAME:新类型的名称,去除修饰,全大写如(DOUBLE
  • ParentName:父类型的名称,驼峰形式(如GObject

G_DECLARE_DERIVABLE_TYPE内部完成以下操作:

  • 声明返回类型为GType<moudle>_<name>_get_type()函数:
    这只是声明,用户需要定义它。但其实可以使用G_DEFINE_TYPE,其扩展包括该函数的定义。所以,实际上用户不需要手动编写定义。
  • 定义_<Moudle><Name>实例结构体:
    其唯一的成员是其父类型的实例结构体。用户可以在源文件中使用私有结构体来存储实例的变量,这一行为(将不需要公开的实例的变量存储在一个私有的结构体中)是为了封装和隐藏实现细节。然后typedef struct _<Moudle><Name> <Moudle><Name>
  • 定义<Moudle><Name>Class
    简化类结构体的使用,即typedef struct _<Moudle><Name>Class <Moudle><Name>Class。但是_<Moudle><Name>Class结构体本身并没有在宏中定义,用户应该在使用此宏之后在头文件中定义。
  • 定义<MOUDLE>_<NAME>()<MOUDLE>_<NAME>_CLASS()静态内联函数:
    <MOUDLE>_<NAME>()将参数转换为指向该类型实例的指针。<MOUDLE>_<NAME>_CLASS()同理。
  • 定义<MOUDLE>_IS_<NAME>()<MOUDLE>_IS_<NAME>_CLASS()函数:
    <MOUDLE>_IS_<NAME>()会检查参数是否为指向<Moudle><Name>类型实例的指针。如果参数指向<Moudle><Name>及其后代,则宏返回真。<MOUDLE>_IS_<NAME>_CLASS()同理。
  • 定义<MOUDLE>_<NAME>_GET_CLASS()函数:
    接受一个指向<Moudle><Name>类型(或其子类型)的实例的指针,获取指向<Moudle><Name>Class的指针。

在使用G_DECLARE_DERIVABLE_TYPE之前,我们还要手动定义<MOUDLE>_TYPE_<NAME>

对于一个类型的声明,从使用G_DECLARE_FINAL_TYPE()变成使用G_DECLARE_DERIVABLE_TYPE(),是不会破坏API或ABI的。所以在我们平时使用中,如果一开始无法判断类型的可派生性,建议先使用G_DECLARE_FINAL_TYPE(),直到我们确定该类型的派生是有意义的,再使用相应的宏。

G_DECLARE_FINAL_TYPE

详见上一节

G_DEFINE_ABSTRACT_TYPE

类似于G_DEFINE_TYPE,但是是定义一个抽象类型。

示例代码

PawNumber

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//file: PawNumber.h
#ifndef PAW_NUMBER_H
#define PAW_NUMBER_H

#include <glib-object.h>

G_BEGIN_DECLS

#define PAW_TYPE_NUMBER (paw_number_get_type())
G_DECLARE_DERIVABLE_TYPE(PawNumber, paw_number, PAW, NUMBER, GObject)

// public class structure
struct _PawNumberClass
{
GObjectClass parent_class;
/* virtual functions */
PawNumber* (*mul)(PawNumber* self, PawNumber* other);
PawNumber* (*div)(PawNumber* self, PawNumber* other);
PawNumber* (*uminus)(PawNumber* self);
char* (*to_s)(PawNumber* self);
};

/* arithmetic operator */
/* These operators create a new instance and return a pointer to it. */
PawNumber* paw_number_mul(PawNumber* self, PawNumber* other);

PawNumber* paw_number_div(PawNumber* self, PawNumber* other);

PawNumber* paw_number_uminus(PawNumber* self);

char* paw_number_to_s(PawNumber* self);

G_END_DECLS

#endif // PAW_NUMBER_H
  •   7,33:宏,用于在C++环境下保持C语言链接约定,确保C函数不会被当作C++函数进行名称修饰。
  •       10:声明可派生类型PawNumber。
  • 13-21:定义类结构体PawNumberClass
  • 25-31:定义公共函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//file: PawNumber.c
#include "PawNumber.h"

G_DEFINE_ABSTRACT_TYPE(PawNumber, paw_number, G_TYPE_OBJECT)

PawNumber* paw_number_mul(PawNumber* self, PawNumber* other)
{
g_return_val_if_fail(PAW_IS_NUMBER(self), NULL);
g_return_val_if_fail(PAW_IS_NUMBER(other), NULL);

PawNumberClass* class = PAW_NUMBER_GET_CLASS(self);

return class->mul ? class->mul(self, other) : NULL;
}

PawNumber* paw_number_div(PawNumber* self, PawNumber* other)
{
g_return_val_if_fail(PAW_IS_NUMBER(self), NULL);
g_return_val_if_fail(PAW_IS_NUMBER(other), NULL);

PawNumberClass* class = PAW_NUMBER_GET_CLASS(self);

return class->div ? class->div(self, other) : NULL;
}

PawNumber* paw_number_uminus(PawNumber* self)
{
g_return_val_if_fail(PAW_IS_NUMBER(self), NULL);

PawNumberClass* class = PAW_NUMBER_GET_CLASS(self);

return class->uminus ? class->uminus(self) : NULL;
}

char* paw_number_to_s(PawNumber* self)
{
g_return_val_if_fail(PAW_IS_NUMBER(self), NULL);

PawNumberClass* class = PAW_NUMBER_GET_CLASS(self);

return class->to_s ? class->to_s(self) : NULL;
}

static void paw_number_class_init(PawNumberClass* class)
{
/* virtual functions */
class->mul = NULL;
class->div = NULL;
class->uminus = NULL;
class->to_s = NULL;
}

static void paw_number_init(PawNumber* self)
{
}
  •         4:定义抽象类型PawNumber
  •   6-42:公共函数,如果子类有提供重写,则调用子类的实现;如果没有,则不进行任何操作。
  • 44-51:类初始化函数
  • 47-50:虚函数,它们期望被子类重写,所以在此抽象类中赋值为NULL,意味着它们在基类中的默认实现就是什么也不做。
  • 53-55:实例初始化函数。由于抽象类型无法被实例化,因此该函数什么也不做。不过,不能省略该函数的定义。

PawInt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//file: PawInt.h
#ifndef PAW_INT_H
#define PAW_INT_H
#include <glib-object.h>

G_BEGIN_DECLS

#define PAW_TYPE_INT (paw_int_get_type())
G_DECLARE_FINAL_TYPE(PawInt, paw_int, PAW, INT, PawNumber)

// 为了演示,不是好的设计,破坏了封装性,实际使用应定义在源文件中
struct _PawInt
{
PawNumber parent;
int value;
};

PawInt* paw_int_new(int value);

G_END_DECLS

#endif // PAW_INT_H
  •   6,20:宏,用于在C++环境下保持C语言链接约定,确保C函数不会被当作C++函数进行名称修饰。
  •        9:声明最终类型PawInt
  • 12-16:定义实例结构体
  •       18:声明公共函数。算术函数和to_s函数在PawNumber中声明,所以PawInt无需再次声明。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
//file: PawInt.c
#include "PawNumber.h"
#include "PawInt.h"
#include "PawDouble.h"

G_DEFINE_TYPE(PawInt, paw_int, PAW_TYPE_NUMBER)

// 为了演示,不是好的设计,不应直接访问value成员,破坏了封装性。后续会在“属性”一节提出优化方案
// 而且会损失精度
#define paw_int_binary_op(op)\
int i;\
double d;\
if(PAW_IS_INT(other))\
{\
i = PAW_INT(other)->value;\
return PAW_NUMBER(paw_int_new(PAW_INT(self)->value op i));\
}\
else\
{\
d = PAW_DOUBLE(other)->value;\
return PAW_NUMBER(paw_int_new(PAW_INT(self)->value op (int)d));\
}

static PawNumber* paw_int_mul(PawNumber* self, PawNumber* other)
{
g_return_val_if_fail(PAW_IS_INT(self), NULL);

paw_int_binary_op(*);
}

static PawNumber* paw_int_div(PawNumber* self, PawNumber* other)
{
g_return_val_if_fail(PAW_IS_INT(self), NULL);
//不是好的设计,没有考虑除数为0的情况,后续优化方案见“信号”一节
paw_int_binary_op(/);
}

static PawNumber* paw_int_uminus(PawNumber* self)
{
g_return_val_if_fail(PAW_IS_INT(self), NULL);

return PAW_NUMBER(paw_int_new(-PAW_INT(self)->value));
}

static char* paw_int_to_s(PawNumber* self)
{
g_return_val_if_fail(PAW_IS_INT(self), NULL);

PawInt* int_self = PAW_INT(self);
return g_strdup_printf("%d", int_self->value);
}

PawInt* paw_int_new(int value)
{
PawInt* d;
d = g_object_new(PAW_TYPE_INT, NULL);
d->value = value;
return d;
}

static void paw_int_class_init(PawIntClass* class)
{
PawNumberClass* number_class = PAW_NUMBER_CLASS(class);

/* override virtual functions */
number_class->mul = paw_int_mul;
number_class->div = paw_int_div;
number_class->uminus = paw_int_uminus;
number_class->to_s = paw_int_to_s;
}

static void paw_int_init(PawInt* self)
{
}
  •         6:定义类型PawInt。
  • 10-22:定义了一个宏,用于对PawInt和PawDouble类型实例执行二元算术操作,并返回一个新的PawNumber类型实例。
  • 24-36:使用paw_int_binary_op进行的乘,除操作。
  • 38-43:一元负号运算,取反操作。
  • 45-51:to_s函数,将整形转换为字符串。调用者负责释放创建出的字符串。
  • 61-70:类初始化函数。将子类实现的乘,除等操作绑定到基类的函数指针上以达到重写的目的。
  • 72-74:实例初始化函数。

PawDouble

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//file: PawDouble.h
#ifndef PAW_DOUBLE_H
#define PAW_DOUBLE_H
#include <glib-object.h>

G_BEGIN_DECLS

#define PAW_TYPE_DOUBLE (paw_double_get_type())
G_DECLARE_FINAL_TYPE(PawDouble, paw_double, PAW, DOUBLE, PawNumber)

struct _PawDouble
{
PawNumber parent;
double value;
};

PawDouble* paw_double_new(double value);

G_END_DECLS

#endif // PAW_DOUBLE_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
//file: PawDouble.c
#include "PawNumber.h"
#include "PawDouble.h"
#include "PawInt.h"

G_DEFINE_TYPE(PawDouble, paw_double, PAW_TYPE_NUMBER)

/* arithmetic operator */
/* These operators create a new instance and return a pointer to it. */
#define paw_double_binary_op(op)\
int i;\
double d;\
if(PAW_IS_INT(other))\
{\
i = PAW_INT(other)->value;\
return PAW_NUMBER(paw_double_new(PAW_DOUBLE(self)->value op (double)i));\
}\
else\
{\
d = PAW_DOUBLE(other)->value;\
return PAW_NUMBER(paw_double_new(PAW_DOUBLE(self)->value op d));\
}

static PawNumber* paw_double_mul(PawNumber* self, PawNumber* other)
{
g_return_val_if_fail(PAW_IS_DOUBLE(self), NULL);

paw_double_binary_op(*);
}

static PawNumber* paw_double_div(PawNumber* self, PawNumber* other)
{
g_return_val_if_fail(PAW_IS_DOUBLE(self), NULL);

//对于除数为0的情况,将在信号一节中讨论
paw_double_binary_op(/);
}

static PawNumber* paw_double_uminus(PawNumber* self)
{
g_return_val_if_fail(PAW_IS_DOUBLE(self), NULL);

return PAW_NUMBER(paw_double_new(- PAW_DOUBLE(self)->value));
}

static char* paw_double_to_s(PawNumber* self)
{
g_return_val_if_fail(PAW_IS_DOUBLE(self), NULL);

PawDouble* double_self = PAW_DOUBLE(self);
return g_strdup_printf("%lf", double_self->value);
}

PawDouble* paw_double_new(double value)
{
PawDouble* d;
d = g_object_new(PAW_TYPE_DOUBLE, NULL);
d->value = value;
return d;
}

static void paw_double_class_init(PawDoubleClass* class)
{
PawNumberClass* number_class = PAW_NUMBER_CLASS(class);

/* override virtual functions */
number_class->mul = paw_double_mul;
number_class->div = paw_double_div;
number_class->uminus = paw_double_uminus;
number_class->to_s = paw_double_to_s;
}

static void paw_double_init(PawDouble* self)
{
}

内容类似于PawInt,此处不赘述。

main函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//file: main.c
#include <glib-object.h>
#include "PawNumber.h"
#include "PawInt.h"
#include "PawDouble.h"

int main(int argc, char **argv)
{
PawNumber *i, *d, *num;
char *si, *sd, *snum;

i = PAW_NUMBER(paw_int_new(23));
d = PAW_NUMBER(paw_double_new(10.3));

num = paw_number_mul(i, d);

si = paw_number_to_s(i);
sd = paw_number_to_s(d);
snum = paw_number_to_s(num);

g_print("%s * %s is %s\n", si, sd, snum);

g_object_unref(num);
g_free(snum);

num = paw_number_mul(d, i);
snum = paw_number_to_s(num);

g_print("%s * %s is %s\n", sd, si, snum);

g_object_unref(num);
g_free(snum);

g_free(sd);
g_free(si);

g_object_unref(d);
g_object_unref(i);

return 0;
}

主函数的功能较简单:

  • 12-13:创建PawDouble和PawInt实例并初始化
  •       15:给i乘上d
  •      26:给d乘上i

总结

上述代码还存在着一些缺陷,由于还没有掌握相关知识,将在后续章节中优化,现总结如下:

  • 对于除运算,没有对除数为0的特殊情况做处理。这将在《信号章节》中进行优化。
  • 对于二元运算宏paw_int_binary_oppaw_double_binary_op,由于其内部要使用类型实例的value属性,因此不得不把实例结构体_PawInt_PawDouble定义在头文件中,将细节暴露给了外部代码,破坏了封装性。这将在《属性章节》中进行优化。

参考文章

1.GObject Tutorial for beginners

  • 标题: GObject学习笔记(三)可派生抽象类型
  • 作者: paw5zx
  • 创建于 : 2024-11-13 22:14:13
  • 更新于 : 2024-12-09 11:42:43
  • 链接: https://paw5zx.github.io/GObject-tutorial-beginner-03/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论