接口类似于抽象类。接口定义了虚拟函数,这些函数预期会被另一个可实例化对象中的函数覆盖。

本节提供了一个简单的例子,TComparable。TComparable 是一个接口。它定义了比较的函数,这些函数包括:

  • t_comparable_cmp(self, other):它比较 self 和 other。第一个参数 self 是运行 t_comparable_cmp 的实例。第二个参数 other 是另一个实例。这个函数需要在实现了该接口的对象中被覆盖。

    • 如果 self 等于 other,t_comparable_cmp 返回 0。

    • 如果 self 大于 other,t_comparable_cmp 返回 1。

    • 如果 self 小于 other,t_comparable_cmp 返回 -1。

    • 如果发生错误,t_comparable_cmp 返回 -2。

  • t_comparable_eq(self, other):如果 self 等于 other,则返回 TRUE。否则返回 FALSE。需要注意的是,即使发生错误也会返回 FALSE。

  • t_comparable_gt(self, other):如果 self 大于 other,则返回 TRUE。否则返回 FALSE。

  • t_comparable_lt(self, other):如果 self 小于 other,则返回 TRUE。否则返回 FALSE。

  • t_comparable_ge(self, other):如果 self 大于或等于 other,则返回 TRUE。否则返回 FALSE。

  • t_comparable_le(self, other):如果 self 小于或等于 other,则返回 TRUE。否则返回 FALSE。

数字和字符串是可比较的。TInt、TDouble 和 TStr 实现了 TComparable 接口,因此它们可以使用上述函数。此外,TNumStr 也可以使用这些函数,因为它是 TStr 的子类。

例如,

TInt *i1 = t_int_new_with_value (10);
TInt *i2 = t_int_new_with_value (20);
t_comparable_eq (T_COMPARABLE (i1), T_COMPARABLE (i2)); /* => FALSE */
t_comparable_lt (T_COMPARABLE (i1), T_COMPARABLE (i2)); /* => TRUE */

接口和抽象类之间的区别是什么?在抽象类中的虚拟函数由其后代类中的函数覆盖。在接口中的虚拟函数可以由任何类中的函数覆盖。比较一下 TNumber 和 TComparable。

  • 函数 t_number_add 在 TIntClass 和 TDoubleClass 中被覆盖。它不能在 TStrClass 中被覆盖,因为 TStr 不是 TNumber 的后代。

  • 函数 t_comparable_cmp 在 TIntClass、TDoubleClass 和 TStrClass 中被覆盖。

1 TComparable 接口

定义接口与定义对象类似。

  • 使用 G_DECLARE_INTERFACE 代替 G_DECLARE_FINAL_TYPE。

  • 使用 G_DEFINE_INTERFACE 代替 G_DEFINE_TYPE。

1.1 头文件 tcomparable.h

现在让我们看看头文件。

 1 #pragma once
 2 
 3 #include <glib-object.h>
 4 
 5 #define T_TYPE_COMPARABLE  (t_comparable_get_type ())
 6 G_DECLARE_INTERFACE (TComparable, t_comparable, T, COMPARABLE, GObject)
 7 /* 这其实是一个类结构体,接口没有实例结构体(压根就没有实例结构体的定义) */
 8 struct _TComparableInterface {
 9   GTypeInterface parent; /* 注意:第一个成员是 GTypeInterface parent */
10   /* 信号 */
11   void (*arg_error) (TComparable *self);
12   /* 虚函数 */
13   int (*cmp) (TComparable *self, TComparable *other);
14 };
15 
16 /* t_comparable_cmp */
17 /* if self > other, then returns 1 */
18 /* if self = other, then returns 0 */
19 /* if self < other, then returns -1 */
20 /* if error happens, then returns -2 */
21 
22 int
23 t_comparable_cmp (TComparable *self, TComparable *other);
24 
25 gboolean
26 t_comparable_eq (TComparable *self, TComparable *other);
27 
28 gboolean
29 t_comparable_gt (TComparable *self, TComparable *other);
30 
31 gboolean
32 t_comparable_lt (TComparable *self, TComparable *other);
33 
34 gboolean
35 t_comparable_ge (TComparable *self, TComparable *other);
36 
37 gboolean
38 t_comparable_le (TComparable *self, TComparable *other);
  • 6: G_DECLARE_INTERFACE宏。最后一个参数是接口的先决条件。TComparable的先决条件是GObject。因此,除了GObject的派生类之外,例如GVariant,不能实现TComparable。先决条件是接口或类的GType。此宏扩展为:

    /************************************G_DECLARE_INTERFACE宏展开*********************START**********************/
    
    GType t_comparable_get_type (void);
    
    # 4 "/home/lieryang/Desktop/gobject-study/section-two/sec9/interface.c"
    #pragma GCC diagnostic push
    # 4 "/home/lieryang/Desktop/gobject-study/section-two/sec9/interface.c"
    
    # 4 "/home/lieryang/Desktop/gobject-study/section-two/sec9/interface.c"
    #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
    # 4 "/home/lieryang/Desktop/gobject-study/section-two/sec9/interface.c"
    
    typedef struct _TComparable TComparable; 
    typedef struct _TComparableInterface TComparableInterface; 
    typedef TComparable *TComparable_autoptr; 
    typedef GList *TComparable_listautoptr; 
    typedef GSList *TComparable_slistautoptr; 
    typedef GQueue *TComparable_queueautoptr;
    
    # 4 "/home/lieryang/Desktop/gobject-study/section-two/sec9/interface.c"
    #pragma GCC diagnostic push
    # 4 "/home/lieryang/Desktop/gobject-study/section-two/sec9/interface.c"
    
    # 4 "/home/lieryang/Desktop/gobject-study/section-two/sec9/interface.c"
    #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
    # 4 "/home/lieryang/Desktop/gobject-study/section-two/sec9/interface.c"
    
    static __attribute__ ((__unused__)) inline void 
    glib_autoptr_clear_TComparable (TComparable *_ptr) { 
      if (_ptr) (glib_autoptr_clear_GObject) ((GObject *) _ptr); 
    } 
    
    static __attribute__ ((__unused__)) inline void 
    glib_autoptr_cleanup_TComparable (TComparable **_ptr) { 
      glib_autoptr_clear_TComparable (*_ptr); 
    } 
    
    static __attribute__ ((__unused__)) inline void 
    glib_listautoptr_cleanup_TComparable (GList **_l) { 
      g_list_free_full (*_l, (GDestroyNotify) (void(*)(void)) glib_autoptr_clear_GObject); 
    } 
    
    static __attribute__ ((__unused__)) inline void 
    glib_slistautoptr_cleanup_TComparable (GSList **_l) { 
      g_slist_free_full (*_l, (GDestroyNotify) (void(*)(void)) glib_autoptr_clear_GObject); 
    } 
    static __attribute__ ((__unused__)) inline void 
    glib_queueautoptr_cleanup_TComparable (GQueue **_q) { 
      if (*_q) g_queue_free_full (*_q, (GDestroyNotify) (void(*)(void)) glib_autoptr_clear_GObject); 
    }
    
    # 4 "/home/lieryang/Desktop/gobject-study/section-two/sec9/interface.c"
    #pragma GCC diagnostic pop
    # 4 "/home/lieryang/Desktop/gobject-study/section-two/sec9/interface.c"
    __attribute__ ((__unused__)) static inline TComparable * 
    T_COMPARABLE (gpointer ptr) { 
      return (((TComparable*) (void *) g_type_check_instance_cast ((GTypeInstance*) (ptr), (t_comparable_get_type ())))); 
    } 
    
    __attribute__ ((__unused__)) static inline gboolean 
    T_IS_COMPARABLE (gpointer ptr) { 
      return ((__extension__ ({ GTypeInstance *__inst = (GTypeInstance*) (ptr); 
                                GType __t = (t_comparable_get_type ()); 
                                gboolean __r; 
                                if (!__inst) __r = (0); 
                                else if (__inst->g_class && __inst->g_class->g_type == __t) __r = (!(0)); 
                                else __r = g_type_check_instance_is_a (__inst, __t); __r; }))); 
    } 
    
    __attribute__ ((__unused__)) static inline TComparableInterface * 
    T_COMPARABLE_GET_IFACE (gpointer ptr) { 
      return (((TComparableInterface*) g_type_interface_peek (((GTypeInstance*) (ptr))->g_class, (t_comparable_get_type ())))); 
    }
    
    # 4 "/home/lieryang/Desktop/gobject-study/section-two/sec9/interface.c"
    #pragma GCC diagnostic pop
    # 4 "/home/lieryang/Desktop/gobject-study/section-two/sec9/interface.c"
    
    
    /************************************G_DECLARE_INTERFACE宏展开*********************END**********************/
    
    • 声明 GType t_comparable_get_type (void);

    • 定义 typedef struct _TComparableInterface TComparableInterface

    • 定义 T_COMPARABLE()宏。它将一个实例转换为TComparable类型。

    • 定义 T_IS_COMPARABLE()宏。它检查实例的类型是否为T_TYPE_COMPARABLE。

    • 定义 T_COMPARABLE_GET_IFACE()宏。它获取给定作为参数的实例的接口。

  • 8-14: TComparableInterface结构。这类似于类结构。第一个成员是父接口 。TComparableInterface的父接口是GTypeInterface。GTypeInterface是所有接口类型的基类。它类似于是GTypeClass,它是所有类类型的基类。GTypeClass是结构GObjectClass的第一个成员。 请参阅gobject.h。请注意,GObjectClass与struct _GObjectClass相同)。

    /* GTypeInterface有两个成员,一个是接口类型id,另一个是实例 */
    struct _GTypeInterface
    {
      /*< private >*/
      GType g_type;         /* iface type */
      GType g_instance_type;
    };
    

    下一个成员是指向“arg-error”信号的默认信号处理程序arg_error的指针。 当比较函数的第二个参数不合适时,将触发此信号。例如,如果self是TInt,other是TStr,它们都是Comparable实例。但它们不能进行比较。这是因为other不是TNumber。最后一个成员cmp是指向比较方法的指针。这是一个虚函数,预计将由实现接口的对象中的函数覆盖。

  • 22-38: 公共函数。

1.2 源文件 tcomparable.c

C文件tcomparable.c如下所示:

 1 #include "tcomparable.h"
 2 
 3 static guint t_comparable_signal;
 4 
 5 G_DEFINE_INTERFACE (TComparable, t_comparable, G_TYPE_OBJECT)
 6 
 7 static void
 8 arg_error_default_cb (TComparable *self) {
 9   g_printerr ("\nTComparable: argument error.\n");
10 }
11 
12 static void
13 t_comparable_default_init (TComparableInterface *iface) {
14   /* virtual function */
15   iface->cmp = NULL;
16   /* argument error signal */
17   iface->arg_error = arg_error_default_cb;
18   t_comparable_signal =
19   g_signal_new ("arg-error",
20                 T_TYPE_COMPARABLE,
21                 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
22                 G_STRUCT_OFFSET (TComparableInterface, arg_error),
23                 NULL /* accumulator */,
24                 NULL /* accumulator data */,
25                 NULL /* C marshaller */,
26                 G_TYPE_NONE /* return_type */,
27                 0     /* n_params */
28                 );
29 }
30 
31 int
32 t_comparable_cmp (TComparable *self, TComparable *other) {
33   g_return_val_if_fail (T_IS_COMPARABLE (self), -2);
34 
35   TComparableInterface *iface = T_COMPARABLE_GET_IFACE (self);
36   
37   return (iface->cmp == NULL ? -2 : iface->cmp (self, other));
38 }
39 
40 gboolean
41 t_comparable_eq (TComparable *self, TComparable *other) {
42   return (t_comparable_cmp (self, other) == 0);
43 }
44 
45 gboolean
46 t_comparable_gt (TComparable *self, TComparable *other) {
47   return (t_comparable_cmp (self, other) == 1);
48 }
49 
50 gboolean
51 t_comparable_lt (TComparable *self, TComparable *other) {
52   return (t_comparable_cmp (self, other) == -1);
53 }
54 
55 gboolean
56 t_comparable_ge (TComparable *self, TComparable *other) {
57   int result = t_comparable_cmp (self, other);
58   return (result == 1 || result == 0);
59 }
60 
61 gboolean
62 t_comparable_le (TComparable *self, TComparable *other) {
63   int result = t_comparable_cmp (self, other);
64   return (result == -1 || result == 0);
65 }
  • 5: G_DEFINE_INTERFACE宏。第三个参数是前提条件的类型。此宏扩展为:
    /************************************G_DEFINE_INTERFACE宏展开*********************START**********************/
    static void t_comparable_default_init (TComparableInterface *klass);
    GType t_comparable_get_type (void) { 
      static gsize static_g_define_type_id = 0; 
      if ((__extension__ ({ _Static_assert (sizeof *(&static_g_define_type_id) == sizeof (gpointer), "Expression evaluates to false"); 
        (void) (0 ? (gpointer) *(&static_g_define_type_id) : ((void *)0)); 
        (!(__extension__ ({ _Static_assert (sizeof *(&static_g_define_type_id) == sizeof (gpointer), "Expression evaluates to false"); 
        __typeof__ (*(&static_g_define_type_id)) gapg_temp_newval; 
        __typeof__ ((&static_g_define_type_id)) gapg_temp_atomic = (&static_g_define_type_id); 
        __atomic_load (gapg_temp_atomic, &gapg_temp_newval, 5); 
        gapg_temp_newval; 
        })) && g_once_init_enter (&static_g_define_type_id)); 
      }))) {
        
        GType g_define_type_id = g_type_register_static_simple (((GType) ((2) << (2))), 
                                                                g_intern_static_string ("TComparable"), 
                                                                sizeof (TComparableInterface), 
                                                                (GClassInitFunc)(void (*)(void)) t_comparable_default_init, 
                                                                0, 
                                                                (GInstanceInitFunc) ((void *)0), 
                                                                (GTypeFlags) 0); 
          
        if (((GType) ((20) << (2))) != ((GType) ((0) << (2)))) 
          g_type_interface_add_prerequisite (g_define_type_id, ((GType) ((20) << (2)))); 
            
          { {;;} } 
            
          (__extension__ ({ 
            _Static_assert (sizeof *(&static_g_define_type_id) == sizeof (gpointer), "Expression evaluates to false"); 
            0 ? (void) (*(&static_g_define_type_id) = (g_define_type_id)) : (void) 0; g_once_init_leave ((&static_g_define_type_id), (gsize) (g_define_type_id)); 
          })); 
      } 
      return static_g_define_type_id; 
    }
    /************************************G_DEFINE_INTERFACE宏展开*********************END**********************/
    
    • 声明 t_comparable_default_init()。

    • 定义 t_comparable_get_type()。

  • 7-10: arg_error_default_cb是”arg-error”信号的默认信号处理程序。

  • 12-29: t_comparable_default_init函数。此函数类似于类初始化函数。它初始化TComparableInterface结构。

  • 15: 将cmp赋值为NULL。因此,在实现类覆盖它之前,比较方法不起作用。

  • 17: 设置”arg-error”信号的默认信号处理程序。

  • 18-28: 创建”arg-error”信号。

  • 31-38: 函数t_comparable_cmp。它首先检查self的类型。如果它不可比较,它会记录错误消息并返回-2(错误)。如果iface->cmp为NULL(这意味着类方法还没有被覆盖),则返回-2。否则,它调用类方法并返回类方法返回的值。

  • 40-65: 公共函数。这五个函数都基于 t_comparable_cmp 。因此,它们不需要被覆盖。例如,t_comparable_eq只是调用t_comparable_cmp。如果t_comparable_cmp返回零,它返回TRUE。否则,它返回FALSE。

这个程序使用信号向用户提供参数类型错误信息。这种错误通常是程序错误,而不是运行时错误。使用信号报告程序错误并不是一个好方法。最好的方法是使用g_return_if_fail。我之所以使用这个信号,只是想演示如何在接口中实现信号。

2 Implementing interface

TInt、TDouble 和 TStr 实现了 TComparable。首先看一下 TInt。头文件与之前相同。实现是在 C 文件中编写的。

2.1 源文件 tint.c

tint.c 如下:

  1 #include "../tnumber/tnumber.h"
  2 #include "../tnumber/tint.h"
  3 #include "../tnumber/tdouble.h"
  4 #include "tcomparable.h"
  5 
  6 enum {
  7   PROP_0,
  8   PROP_INT,
  9   N_PROPERTIES
 10 };
 11 
 12 static GParamSpec *int_properties[N_PROPERTIES] = {NULL, };
 13 
 14 struct _TInt {
 15   TNumber parent;
 16   int value;
 17 };
 18 
 19 static void t_comparable_interface_init (TComparableInterface *iface);
 20 
 21 G_DEFINE_TYPE_WITH_CODE (TInt, t_int, T_TYPE_NUMBER,
 22                          G_IMPLEMENT_INTERFACE (T_TYPE_COMPARABLE, t_comparable_interface_init))
 23 
 24 static int
 25 t_int_comparable_cmp (TComparable *self, TComparable *other) {
 26   if (! T_IS_NUMBER (other)) {
 27     g_signal_emit_by_name (self, "arg-error");
 28     return -2;
 29   }
 30 
 31   int i;
 32   double s, o;
 33 
 34   s = (double) T_INT (self)->value;
 35   if (T_IS_INT (other)) {
 36     g_object_get (other, "value", &i, NULL);
 37     o = (double) i;
 38   } else
 39     g_object_get (other, "value", &o, NULL);
 40   if (s > o)
 41     return 1;
 42   else if (s == o)
 43     return 0;
 44   else if (s < o)
 45     return -1;
 46   else /* This can't happen. */
 47     return -2;
 48 }
 49 
 50 static void
 51 t_comparable_interface_init (TComparableInterface *iface) {
 52   iface->cmp = t_int_comparable_cmp;
 53 }
 54 
 55 static void
 56 t_int_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) {
 57   TInt *self = T_INT (object);
 58 
 59   if (property_id == PROP_INT)
 60     self->value = g_value_get_int (value);
 61   else
 62     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
 63 }
 64 
 65 static void
 66 t_int_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) {
 67   TInt *self = T_INT (object);
 68 
 69   if (property_id == PROP_INT)
 70     g_value_set_int (value, self->value);
 71   else
 72     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
 73 }
 74 
 75 static void
 76 t_int_init (TInt *self) {
 77 }
 78 
 79 /* arithmetic operator */
 80 /* These operators create a new instance and return a pointer to it. */
 81 #define t_int_binary_op(op) \
 82   int i; \
 83   double d; \
 84   if (T_IS_INT (other)) { \
 85     g_object_get (T_INT (other), "value", &i, NULL); \
 86     return  T_NUMBER (t_int_new_with_value (T_INT(self)->value op i)); \
 87   } else { \
 88     g_object_get (T_DOUBLE (other), "value", &d, NULL); \
 89     return  T_NUMBER (t_int_new_with_value (T_INT(self)->value op (int) d)); \
 90   }
 91 
 92 static TNumber *
 93 t_int_add (TNumber *self, TNumber *other) {
 94   g_return_val_if_fail (T_IS_INT (self), NULL);
 95 
 96   t_int_binary_op (+)
 97 }
 98 
 99 static TNumber *
100 t_int_sub (TNumber *self, TNumber *other) {
101   g_return_val_if_fail (T_IS_INT (self), NULL);
102 
103   t_int_binary_op (-)
104 }
105 
106 static TNumber *
107 t_int_mul (TNumber *self, TNumber *other) {
108   g_return_val_if_fail (T_IS_INT (self), NULL);
109 
110   t_int_binary_op (*)
111 }
112 
113 static TNumber *
114 t_int_div (TNumber *self, TNumber *other) {
115   g_return_val_if_fail (T_IS_INT (self), NULL);
116 
117   int i;
118   double d;
119 
120   if (T_IS_INT (other)) {
121     g_object_get (T_INT (other), "value", &i, NULL);
122     if (i == 0) {
123       g_signal_emit_by_name (self, "div-by-zero");
124       return NULL;
125     } else
126       return  T_NUMBER (t_int_new_with_value (T_INT(self)->value / i));
127   } else {
128     g_object_get (T_DOUBLE (other), "value", &d, NULL);
129     if (d == 0) {
130       g_signal_emit_by_name (self, "div-by-zero");
131       return NULL;
132     } else
133       return  T_NUMBER (t_int_new_with_value (T_INT(self)->value / (int)  d));
134   }
135 }
136 
137 static TNumber *
138 t_int_uminus (TNumber *self) {
139   g_return_val_if_fail (T_IS_INT (self), NULL);
140 
141   return T_NUMBER (t_int_new_with_value (- T_INT(self)->value));
142 }
143 
144 static char *
145 t_int_to_s (TNumber *self) {
146   g_return_val_if_fail (T_IS_INT (self), NULL);
147 
148   int i;
149 
150   g_object_get (T_INT (self), "value", &i, NULL); 
151   return g_strdup_printf ("%d", i);
152 }
153 
154 static void
155 t_int_class_init (TIntClass *class) {
156   TNumberClass *tnumber_class = T_NUMBER_CLASS (class);
157   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
158 
159   /* override virtual functions */
160   tnumber_class->add = t_int_add;
161   tnumber_class->sub = t_int_sub;
162   tnumber_class->mul = t_int_mul;
163   tnumber_class->div = t_int_div;
164   tnumber_class->uminus = t_int_uminus;
165   tnumber_class->to_s = t_int_to_s;
166 
167   gobject_class->set_property = t_int_set_property;
168   gobject_class->get_property = t_int_get_property;
169   int_properties[PROP_INT] = g_param_spec_int ("value", "val", "Integer value", G_MININT, G_MAXINT, 0, G_PARAM_READWRITE);
170   g_object_class_install_properties (gobject_class, N_PROPERTIES, int_properties);
171 }
172 
173 TInt *
174 t_int_new_with_value (int value) {
175   TInt *i;
176 
177   i = g_object_new (T_TYPE_INT, "value", value, NULL);
178   return i;
179 }
180 
181 TInt *
182 t_int_new (void) {
183   TInt *i;
184 
185   i = g_object_new (T_TYPE_INT, NULL);
186   return i;
187 }
  • 4:需要包含 TComparable 的头文件。

  • 19:声明 t_comparable_interface_init() 函数。这个声明必须在 G_DEFINE_TYPE_WITH_CODE 宏之前完成。

  • 21-22:G_DEFINE_TYPE_WITH_CODE 宏。最后一个参数是 G_IMPLEMENT_INTERFACE 宏。G_IMPLEMENT_INTERFACE 的第二个参数是 t_comparable_interface_init。这两个宏展开为:

    • 声明 t_int_class_init()。

    • 声明 t_int_init()。

    • 定义静态变量 t_int_parent_class,它指向父类的类结构。

    • 定义 t_int_get_type()。此函数包含 g_type_register_static_simple() 和 g_type_add_interface_static()。g_type_register_static_simple() 是 g_type_register_static() 的便捷版本,它将 TInt 类型注册到类型系统中。g_type_add_interface_static() 将接口类型添加到实例类型中。在 GObject 参考手册的 Interfaces 一节中有一个很好的例子。如果你想知道如何不使用宏来编写代码,请参见 tint_without_macro.c。

  • 24-48:t_int_comparable_cmp 是一个比较 TInt 实例和 TNumber 实例的函数。

  • 26-29:检查 other 的类型。如果参数类型不是 TNumber,则使用 g_signal_emit_by_name 发出 “arg-error” 信号。

  • 34:将 self 转换为 double 类型。

  • 35-39:获取 other 的值,如果它是 TInt,则将值转换为 double。

  • 40-47:比较 s 和 o,返回 1、0、-1 和 -2。

  • 50-53:t_comparable_interface_init。这个函数在 TInt 的初始化过程中被调用。函数 t_int_comparable_cmp 被分配给 iface->cmp。

tdouble.c 与 tint.c 几乎相同。这两个对象可以比较,因为在比较前 int 被转换为 double。

2.2 源文件 tstr.c

  1 #include "../tstr/tstr.h"
  2 #include "tcomparable.h"
  3 
  4 enum {
  5   PROP_0,
  6   PROP_STRING,
  7   N_PROPERTIES
  8 };
  9 
 10 static GParamSpec *str_properties[N_PROPERTIES] = {NULL, };
 11 
 12 typedef struct {
 13   char *string;
 14 } TStrPrivate;
 15 
 16 static void t_comparable_interface_init (TComparableInterface *iface);
 17 
 18 G_DEFINE_TYPE_WITH_CODE (TStr, t_str, G_TYPE_OBJECT,
 19                          G_ADD_PRIVATE (TStr)
 20                          G_IMPLEMENT_INTERFACE (T_TYPE_COMPARABLE, t_comparable_interface_init))
 21 
 22 static void
 23 t_str_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) {
 24   TStr *self = T_STR (object);
 25 
 26 /* The returned value of the function g_value_get_string can be NULL. */
 27 /* The function t_str_set_string calls a class method, */
 28 /* which is expected to rewrite in the descendant object. */
 29   if (property_id == PROP_STRING)
 30     t_str_set_string (self, g_value_get_string (value));
 31   else
 32     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
 33 }
 34 
 35 static void
 36 t_str_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) {
 37   TStr *self = T_STR (object);
 38   TStrPrivate *priv = t_str_get_instance_private (self);
 39 
 40 /* The second argument of the function g_value_set_string can be NULL. */
 41   if (property_id == PROP_STRING)
 42     g_value_set_string (value, priv->string);
 43   else
 44     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
 45 }
 46 
 47 /* This function just set the string. */
 48 /* So, no notify signal is emitted. */
 49 static void
 50 t_str_real_set_string (TStr *self, const char *s) {
 51   TStrPrivate *priv = t_str_get_instance_private (self);
 52 
 53   if (priv->string)
 54     g_free (priv->string);
 55   priv->string = g_strdup (s);
 56 }
 57 
 58 static void
 59 t_str_finalize (GObject *object) {
 60   TStr *self = T_STR (object);
 61   TStrPrivate *priv = t_str_get_instance_private (self);
 62 
 63   if (priv->string)
 64     g_free (priv->string);
 65   G_OBJECT_CLASS (t_str_parent_class)->finalize (object);
 66 }
 67 
 68 static int
 69 t_str_comparable_cmp (TComparable *self, TComparable *other) {
 70   if (! T_IS_STR (other)) {
 71     g_signal_emit_by_name (self, "arg-error");
 72     return -2;
 73   }
 74 
 75   char *s, *o;
 76   int result;
 77 
 78   s = t_str_get_string (T_STR (self));
 79   o = t_str_get_string (T_STR (other));
 80 
 81   if (strcmp (s, o) > 0)
 82     result = 1;
 83   else if (strcmp (s, o) == 0)
 84     result = 0;
 85   else if (strcmp (s, o) < 0)
 86     result = -1;
 87   else /* This can't happen. */
 88     result = -2;
 89   g_free (s);
 90   g_free (o);
 91   return result;
 92 }
 93 
 94 static void
 95 t_comparable_interface_init (TComparableInterface *iface) {
 96   iface->cmp = t_str_comparable_cmp;
 97 }
 98 
 99 static void
100 t_str_init (TStr *self) {
101   TStrPrivate *priv = t_str_get_instance_private (self);
102 
103   priv->string = NULL;
104 }
105 
106 static void
107 t_str_class_init (TStrClass *class) {
108   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
109 
110   gobject_class->finalize = t_str_finalize;
111   gobject_class->set_property = t_str_set_property;
112   gobject_class->get_property = t_str_get_property;
113   str_properties[PROP_STRING] = g_param_spec_string ("string", "str", "string", "", G_PARAM_READWRITE);
114   g_object_class_install_properties (gobject_class, N_PROPERTIES, str_properties);
115 
116   class->set_string = t_str_real_set_string;
117 }
118 
119 /* setter and getter */
120 void
121 t_str_set_string (TStr *self, const char *s) {
122   g_return_if_fail (T_IS_STR (self));
123   TStrClass *class = T_STR_GET_CLASS (self);
124 
125 /* The setter calls the class method 'set_string', */
126 /* which is expected to be overridden by the descendant TNumStr. */
127 /* Therefore, the behavior of the setter is different between TStr and TNumStr. */
128   class->set_string (self, s);
129 }
130 
131 char *
132 t_str_get_string (TStr *self) {
133   g_return_val_if_fail (T_IS_STR (self), NULL);
134   TStrPrivate *priv = t_str_get_instance_private (self);
135 
136   return g_strdup (priv->string);
137 }
138 
139 TStr *
140 t_str_concat (TStr *self, TStr *other) {
141   g_return_val_if_fail (T_IS_STR (self), NULL);
142   g_return_val_if_fail (T_IS_STR (other), NULL);
143 
144   char *s1, *s2, *s3;
145   TStr *str;
146 
147   s1 = t_str_get_string (self);
148   s2 = t_str_get_string (other);
149   if (s1 && s2)
150     s3 = g_strconcat (s1, s2, NULL);
151   else if (s1)
152     s3 = g_strdup (s1);
153   else if (s2)
154     s3 = g_strdup (s2);
155   else
156     s3 = NULL;
157   str = t_str_new_with_string (s3);
158   if (s1) g_free (s1);
159   if (s2) g_free (s2);
160   if (s3) g_free (s3);
161   return str;
162 }
163 
164 /* create a new TStr instance */
165 TStr *
166 t_str_new_with_string (const char *s) {
167   return T_STR (g_object_new (T_TYPE_STR, "string", s, NULL));
168 }
169 
170 TStr *
171 t_str_new (void) {
172   return T_STR (g_object_new (T_TYPE_STR, NULL));
173 }
  • 16:声明了 t_comparable_interface_init 函数。在 G_DEFINE_TYPE_WITH_CODE 宏之前需要声明该函数。

  • 18-20:G_DEFINE_TYPE_WITH_CODE 宏。因为 TStr 是可派生类型,所以需要其私有区域(TStrPrivate)。G_ADD_PRIVATE 宏创建了私有区域。注意 G_ADD_PRIVATE 宏后面没有逗号。

  • 68-92:t_str_comparable_cmp 函数。

  • 70-73:检查 other 的类型。如果它不是 TStr,则发出 “arg-error” 信号。

  • 78-79:从 TStr 对象 self 和 other 中获取字符串 s 和 o。

  • 81-88:使用 C 标准函数 strcmp 比较 s 和 o。

  • 89-90:释放 s 和 o。

  • 91:返回结果。

  • 94-97:t_comparable_interface_init 函数。它用 t_str_comparable_cmp 覆盖了 iface->comp。

TStr 可以与 TStr 比较,但不能与 TInt 或 TDouble 比较。通常,比较只在两个同类型实例之间可用。

TNumStr 本身没有实现 TComparable。但它是 TStr 的子类,所以它是可比较的。比较基于字母顺序。所以,”a” 大于 “b”,”three” 大于 “two”。

3 Test program

main.c 是一个测试程序

3.1 main.c

 1 #include <glib-object.h>
 2 #include "tcomparable.h"
 3 #include "../tnumber/tnumber.h"
 4 #include "../tnumber/tint.h"
 5 #include "../tnumber/tdouble.h"
 6 #include "../tstr/tstr.h"
 7 
 8 static void
 9 t_print (const char *cmp, TComparable *c1, TComparable *c2) {
10   char *s1, *s2;
11   TStr *ts1, *ts2, *ts3;
12 
13   ts1 = t_str_new_with_string("\"");
14   if (T_IS_NUMBER (c1))
15     s1 = t_number_to_s (T_NUMBER (c1));
16   else if (T_IS_STR (c1)) {
17     ts2 = t_str_concat (ts1, T_STR (c1));
18     ts3 = t_str_concat (ts2, ts1);
19     s1 = t_str_get_string (T_STR (ts3));
20     g_object_unref (ts2);
21     g_object_unref (ts3);
22   } else {
23     g_print ("c1 isn't TInt, TDouble nor TStr.\n");
24     return;
25   }
26   if (T_IS_NUMBER (c2))
27     s2 = t_number_to_s (T_NUMBER (c2));
28   else if (T_IS_STR (c2)) {
29     ts2 = t_str_concat (ts1, T_STR (c2));
30     ts3 = t_str_concat (ts2, ts1);
31     s2 = t_str_get_string (T_STR (ts3));
32     g_object_unref (ts2);
33     g_object_unref (ts3);
34   } else {
35     g_print ("c2 isn't TInt, TDouble nor TStr.\n");
36     return;
37   }
38   g_print ("%s %s %s.\n", s1, cmp, s2);
39   g_object_unref (ts1);
40   g_free (s1);
41   g_free (s2);
42 }    
43 
44 static void
45 compare (TComparable *c1, TComparable *c2) {
46   if (t_comparable_eq (c1, c2))
47     t_print ("equals", c1, c2);
48   else if (t_comparable_gt (c1, c2))
49     t_print ("is greater than", c1, c2);
50   else if (t_comparable_lt (c1, c2))
51     t_print ("is less than", c1, c2);
52   else if (t_comparable_ge (c1, c2))
53     t_print ("is greater than or equal to", c1, c2);
54   else if (t_comparable_le (c1, c2))
55     t_print ("is less than or equal to", c1, c2);
56   else
57     t_print ("can't compare to", c1, c2);
58 }
59 
60 int
61 main (int argc, char **argv) {
62   const char *one = "one";
63   const char *two = "two";
64   const char *three = "three";
65   TInt *i;
66   TDouble *d;
67   TStr *str1, *str2, *str3;
68 
69   i = t_int_new_with_value (124);
70   d = t_double_new_with_value (123.45);
71   str1 = t_str_new_with_string (one);
72   str2 = t_str_new_with_string (two);
73   str3 = t_str_new_with_string (three);
74 
75   compare (T_COMPARABLE (i), T_COMPARABLE (d));
76   compare (T_COMPARABLE (str1), T_COMPARABLE (str2));
77   compare (T_COMPARABLE (str2), T_COMPARABLE (str3));
78   compare (T_COMPARABLE (i), T_COMPARABLE (str1));
79 
80   g_object_unref (i);
81   g_object_unref (d);
82   g_object_unref (str1);
83   g_object_unref (str2);
84   g_object_unref (str3);
85 
86   return 0;
87 }
  • 8-42:函数 t_print 有三个参数,并构建一个输出字符串,然后将其显示在显示器上。在构建输出时,字符串被双引号包围。

  • 44-58:函数 compare 比较两个 TComparable 对象,并调用 t_print 来显示结果。

  • 60-87:main 函数。

  • 69-73:创建 TInt、TDouble 和三个 TStr 实例。它们被赋予了值。

  • 75:比较 TInt 和 TDouble。

  • 76-77:比较两个 TStr。

  • 78:将 TInt 与 TStr 比较。这会产生一个 “arg-error”。

  • 80-84:释放对象。

4 Compilation and execution

改变目录到 /assets/GObjectStudy/202311/09_GObject/tcomparable/

$ cd ./assets/GObjectStudy/202311/09_GObject/tcomparable/
$ meson setup _build
$ ninja -C _build

执行

$ _build/tcomparable
124 is greater than 123.450000.
"one" is less than "two".
"two" is greater than "three".

TComparable: argument error.

TComparable: argument error.

TComparable: argument error.

TComparable: argument error.

TComparable: argument error.
124 can't compare to "one".

5 Build an interface without macros

我们使用了如 G_DECLARE_INTERFACEG_DEFINE_INTERFACE 等宏来构建接口。并使用 G_DEFINE_TYPE_WITH_CODE 来实现接口。我们也可以不使用宏来构建它。在 tcomparable 目录中有三个文件。

  • tcomparable_without_macro.h

  • tcomparable_without_macro.c

  • tint_without_macro.c

它们不使用宏。相反,它们直接将接口或接口的实现注册到类型系统中。如果你想了解这方面的内容,可以查看 /assets/GObjectStudy/202311/09_GObject/tcomparable/ 中的源文件。

参考

Interface:https://github.com/ToshioCP/Gobject-tutorial/blob/main/gfm/sec9.md