There are two kinds of types, final type and derivable type. Final type doesn’t have any child object. Derivable type has child objects.
The main difference between the two objects is their classes. Final type objects doesn’t have its own class area. The only member of the class is its parent class.
Derivable object has its own area in the class. The class is open to its descendants.
G_DECLARE_DERIVABLE_TYPE
is used to declare derivable
type. It is written in a header file like this:
#define T_TYPE_NUMBER (t_number_get_type ())
(TNumber, t_number, T, NUMBER, GObject) G_DECLARE_DERIVABLE_TYPE
Abstract type doesn’t have any instance. This type of object is derivable and its children can use functions and signals of the abstract object.
The examples of this section are TNumber, TInt and TDouble object. TInt and TDouble have already been made in the previous section. They represent integer and floating point respectively. Numbers are more abstract than integer and floating point.
TNumber is an abstract object which represents numbers. TNumber is a parent object of TInt and TDouble. TNumber isn’t instantiated because its type is abstract. When an instance has TInt or TDouble type, it is an instance of TNumber as well.
TInt and TDouble have five operations: addition, subtraction, multiplication, division and unary minus operation. Those operations can be defined on TNumber object.
In this section we will define TNumber object and five functions
above. In addition, to_s
function will be added. It
converts the value of TNumber into a string. It is like sprintf
function. And we will rewrite TInt and TDouble to implement the
functions.
tnumber.h
is a header file for the TNumber class.
#pragma once
#include <glib-object.h>
#define T_TYPE_NUMBER (t_number_get_type ())
G_DECLARE_DERIVABLE_TYPE (TNumber, t_number, T, NUMBER, GObject)
struct _TNumberClass {
GObjectClass parent_class;
TNumber* (*add) (TNumber *self, TNumber *other);
TNumber* (*sub) (TNumber *self, TNumber *other);
TNumber* (*mul) (TNumber *self, TNumber *other);
TNumber* (*div) (TNumber *self, TNumber *other);
TNumber* (*uminus) (TNumber *self);
char * (*to_s) (TNumber *self);
/* signal */
void (*div_by_zero) (TNumber *self);
};
/* arithmetic operator */
/* These operators create a new instance and return a pointer to it. */
TNumber *
t_number_add (TNumber *self, TNumber *other);
TNumber *
t_number_sub (TNumber *self, TNumber *other);
TNumber *
t_number_mul (TNumber *self, TNumber *other);
TNumber *
t_number_div (TNumber *self, TNumber *other);
TNumber *
t_number_uminus (TNumber *self);
char *
t_number_to_s (TNumber *self);
G_DECLARE_DERIVABLE_TYPE
macro. This is similar to
G_DECLARE_FINAL_TYPE
macro. The difference is derivable or
final. G_DECLARE_DERIVABLE_TYPE
is expanded to:
t_number_get_type ()
function. This
function must be defined in tnumber.c
file. The definition
is usually done with G_DEFINE_TYPE
or its family
macros.T_NUMBER
(cast to instance),
T_NUMBER_CLASS
(cast to class), T_IS_NUMBER
(instance check), T_IS_NUMBER_CLASS
(class check) and
T_NUMBER_GET_CLASS
are defined.g_autoptr()
support.to_s
function. to_s
function is similar to
sprintf function.g_signal_new
as an
argument.tnumber.c
is as follows.
#include "tnumber.h"
static guint t_number_signal;
G_DEFINE_ABSTRACT_TYPE (TNumber, t_number, G_TYPE_OBJECT)
static void
div_by_zero_default_cb (TNumber *self) {
g_printerr ("\nError: division by zero.\n\n");
}
static void
t_number_class_init (TNumberClass *class) {
/* virtual functions */
class->add = NULL;
class->sub = NULL;
class->mul = NULL;
class->div = NULL;
class->uminus = NULL;
class->to_s = NULL;
/* default signal handler */
class->div_by_zero = div_by_zero_default_cb;
/* signal */
t_number_signal =
g_signal_new ("div-by-zero",
G_TYPE_FROM_CLASS (class),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
G_STRUCT_OFFSET (TNumberClass, div_by_zero),
NULL /* accumulator */,
NULL /* accumulator data */,
NULL /* C marshaller */,
G_TYPE_NONE /* return_type */,
0 /* n_params */
);
}
static void
t_number_init (TNumber *self) {
}
TNumber *
t_number_add (TNumber *self, TNumber *other) {
g_return_val_if_fail (T_IS_NUMBER (self), NULL);
g_return_val_if_fail (T_IS_NUMBER (other), NULL);
TNumberClass *class = T_NUMBER_GET_CLASS(self);
return class->add ? class->add (self, other) : NULL;
}
TNumber *
t_number_sub (TNumber *self, TNumber *other) {
g_return_val_if_fail (T_IS_NUMBER (self), NULL);
g_return_val_if_fail (T_IS_NUMBER (other), NULL);
TNumberClass *class = T_NUMBER_GET_CLASS(self);
return class->sub ? class->sub (self, other) : NULL;
}
TNumber *
t_number_mul (TNumber *self, TNumber *other) {
g_return_val_if_fail (T_IS_NUMBER (self), NULL);
g_return_val_if_fail (T_IS_NUMBER (other), NULL);
TNumberClass *class = T_NUMBER_GET_CLASS(self);
return class->mul ? class->mul (self, other) : NULL;
}
TNumber *
t_number_div (TNumber *self, TNumber *other) {
g_return_val_if_fail (T_IS_NUMBER (self), NULL);
g_return_val_if_fail (T_IS_NUMBER (other), NULL);
TNumberClass *class = T_NUMBER_GET_CLASS(self);
return class->div ? class->div (self, other) : NULL;
}
TNumber *
t_number_uminus (TNumber *self) {
g_return_val_if_fail (T_IS_NUMBER (self), NULL);
TNumberClass *class = T_NUMBER_GET_CLASS(self);
return class->uminus ? class->uminus (self) : NULL;
}
char *
t_number_to_s (TNumber *self) {
g_return_val_if_fail (T_IS_NUMBER (self), NULL);
TNumberClass *class = T_NUMBER_GET_CLASS(self);
return class->to_s ? class->to_s (self) : NULL;
}
G_DEFINE_ABSTRACT_TYPE
macro. This macro is used to
define an abstract type object. Abstract type isn’t instantiated. This
macro is expanded to:
t_number_init ()
function.t_number_class_init ()
function.t_number_get_type ()
function.t_number_parent_class
static variable
that points the parent class.div_by_zero_default_cb
is a default handler of
“div-by-zero” signal. Default handler doesn’t have user data parameter.
The function g_signal_new
is used instead of
g_signal_new_class_handler
. It specifies a handler as the
offset from the top of the class to the pointer to the handler.t_number_class_init
.dev_by_zero_default_cb
to
class->div_by_zero
. This is the default handler of
“div-by-zero” signal.t_number_init
is a initialization function for
an instance. But abstract object isn’t instantiated. So, nothing is done
in this function. But you can’t leave out the definition of this
function.tint.h
is a header file of the TInt class. TInt is a
child class of TNumber.
#pragma once
#include <glib-object.h>
#define T_TYPE_INT (t_int_get_type ())
G_DECLARE_FINAL_TYPE (TInt, t_int, T, INT, TNumber)
/* create a new TInt instance */
TInt *
t_int_new_with_value (int value);
TInt *
t_int_new (void);
to_s
are declared in TNumber, so TInt doesn’t declare those
functions. Only instance creation functions are declared.The C file tint.c
implements virtual functions (class
methods). And the pointers of the methods in TNumberClass are rewritten
here.
#include "tnumber.h"
#include "tint.h"
#include "tdouble.h"
#define PROP_INT 1
static GParamSpec *int_property = NULL;
struct _TInt {
TNumber parent;
int value;
};
G_DEFINE_TYPE (TInt, t_int, T_TYPE_NUMBER)
static void
t_int_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) {
TInt *self = T_INT (object);
if (property_id == PROP_INT)
self->value = g_value_get_int (value);
else
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
t_int_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) {
TInt *self = T_INT (object);
if (property_id == PROP_INT)
g_value_set_int (value, self->value);
else
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
t_int_init (TInt *self) {
}
/* arithmetic operator */
/* These operators create a new instance and return a pointer to it. */
#define t_int_binary_op(op) \
int i; \
double d; \
if (T_IS_INT (other)) { \
g_object_get (T_INT (other), "value", &i, NULL); \
return T_NUMBER (t_int_new_with_value (T_INT(self)->value op i)); \
} else { \
g_object_get (T_DOUBLE (other), "value", &d, NULL); \
return T_NUMBER (t_int_new_with_value (T_INT(self)->value op (int) d)); \
}
static TNumber *
t_int_add (TNumber *self, TNumber *other) {
g_return_val_if_fail (T_IS_INT (self), NULL);
t_int_binary_op (+)
}
static TNumber *
t_int_sub (TNumber *self, TNumber *other) {
g_return_val_if_fail (T_IS_INT (self), NULL);
t_int_binary_op (-)
}
static TNumber *
t_int_mul (TNumber *self, TNumber *other) {
g_return_val_if_fail (T_IS_INT (self), NULL);
t_int_binary_op (*)
}
static TNumber *
t_int_div (TNumber *self, TNumber *other) {
g_return_val_if_fail (T_IS_INT (self), NULL);
int i;
double d;
if (T_IS_INT (other)) {
g_object_get (T_INT (other), "value", &i, NULL);
if (i == 0) {
g_signal_emit_by_name (self, "div-by-zero");
return NULL;
} else
return T_NUMBER (t_int_new_with_value (T_INT(self)->value / i));
} else {
g_object_get (T_DOUBLE (other), "value", &d, NULL);
if (d == 0) {
g_signal_emit_by_name (self, "div-by-zero");
return NULL;
} else
return T_NUMBER (t_int_new_with_value (T_INT(self)->value / (int) d));
}
}
static TNumber *
t_int_uminus (TNumber *self) {
g_return_val_if_fail (T_IS_INT (self), NULL);
return T_NUMBER (t_int_new_with_value (- T_INT(self)->value));
}
static char *
t_int_to_s (TNumber *self) {
g_return_val_if_fail (T_IS_INT (self), NULL);
int i;
g_object_get (T_INT (self), "value", &i, NULL);
return g_strdup_printf ("%d", i);
}
static void
t_int_class_init (TIntClass *class) {
TNumberClass *tnumber_class = T_NUMBER_CLASS (class);
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
/* override virtual functions */
tnumber_class->add = t_int_add;
tnumber_class->sub = t_int_sub;
tnumber_class->mul = t_int_mul;
tnumber_class->div = t_int_div;
tnumber_class->uminus = t_int_uminus;
tnumber_class->to_s = t_int_to_s;
gobject_class->set_property = t_int_set_property;
gobject_class->get_property = t_int_get_property;
int_property = g_param_spec_int ("value", "val", "Integer value", G_MININT, G_MAXINT, 0, G_PARAM_READWRITE);
g_object_class_install_property (gobject_class, PROP_INT, int_property);
}
TInt *
t_int_new_with_value (int value) {
TInt *i;
i = g_object_new (T_TYPE_INT, "value", value, NULL);
return i;
}
TInt *
t_int_new (void) {
TInt *i;
i = g_object_new (T_TYPE_INT, NULL);
return i;
}
G_DEFINE_TYPE
.G_DEFINE_TYPE
macro. This macro expands to:
t_int_init ()
function.t_int_get_type ()
function.t_int_parent_class
static variable which
points the parent class.t_int_init
.tnumber.c
.t_int_add
,
t_int_sub
and t_int_mul
. This macro is similar
to t_int_div
function. Refer to the explanation below for
t_int_div
.t_int_add
, t_int_sub
and t_int_mul
. The macro t_int_binary_op
is
used.t_int_div
. The first argument
self
is the object on which the function is called. The
second argument other
is another TNumber object. It can be
TInt or TDouble. If it is TDouble, its value is casted to int before the
division is performed. If the divisor (other
) is zero,
“div-by-zero” signal is emitted. The signal is defined in TNumber, so
TInt doesn’t know the signal id. Therefore, the emission is done with
g_signal_emit_by_name
instead of
g_signal_emit
. The return value of t_int_div
is TNumber type object However, because TNumber is abstract, the actual
type of the object is TInt.to_s
. This function converts int
to string. For example, if the value of the object is 123, then the
result is a string “123”. The caller should free the string if it
becomes useless.t_int_class_init
.t_number_add
is called on a TInt object, then the function
calls the class method *tnumber_class->add
. The pointer
points t_int_add
function. Therefore,
t_int_add
is finally called.TDouble object is defined with tdouble.h
and
tdouble.c
. The definition is very similar to TInt. So, this
subsection just shows the contents of the files.
tdouble.h
#pragma once
#include <glib-object.h>
#define T_TYPE_DOUBLE (t_double_get_type ())
G_DECLARE_FINAL_TYPE (TDouble, t_double, T, DOUBLE, TNumber)
/* create a new TDouble instance */
TDouble *
t_double_new_with_value (double value);
TDouble *
t_double_new (void);
tdouble.c
#include "tnumber.h"
#include "tdouble.h"
#include "tint.h"
#define PROP_DOUBLE 1
static GParamSpec *double_property = NULL;
struct _TDouble {
TNumber parent;
double value;
};
G_DEFINE_TYPE (TDouble, t_double, T_TYPE_NUMBER)
static void
t_double_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) {
TDouble *self = T_DOUBLE (object);
if (property_id == PROP_DOUBLE) {
self->value = g_value_get_double (value);
} else
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
t_double_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) {
TDouble *self = T_DOUBLE (object);
if (property_id == PROP_DOUBLE)
g_value_set_double (value, self->value);
else
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
t_double_init (TDouble *self) {
}
/* arithmetic operator */
/* These operators create a new instance and return a pointer to it. */
#define t_double_binary_op(op) \
int i; \
double d; \
if (T_IS_INT (other)) { \
g_object_get (T_INT (other), "value", &i, NULL); \
return T_NUMBER (t_double_new_with_value (T_DOUBLE(self)->value op (double) i)); \
} else { \
g_object_get (T_DOUBLE (other), "value", &d, NULL); \
return T_NUMBER (t_double_new_with_value (T_DOUBLE(self)->value op d)); \
}
static TNumber *
t_double_add (TNumber *self, TNumber *other) {
g_return_val_if_fail (T_IS_DOUBLE (self), NULL);
t_double_binary_op (+)
}
static TNumber *
t_double_sub (TNumber *self, TNumber *other) {
g_return_val_if_fail (T_IS_DOUBLE (self), NULL);
t_double_binary_op (-)
}
static TNumber *
t_double_mul (TNumber *self, TNumber *other) {
g_return_val_if_fail (T_IS_DOUBLE (self), NULL);
t_double_binary_op (*)
}
static TNumber *
t_double_div (TNumber *self, TNumber *other) {
g_return_val_if_fail (T_IS_DOUBLE (self), NULL);
int i;
double d;
if (T_IS_INT (other)) {
g_object_get (T_INT (other), "value", &i, NULL);
if (i == 0) {
g_signal_emit_by_name (self, "div-by-zero");
return NULL;
} else
return T_NUMBER (t_double_new_with_value (T_DOUBLE(self)->value / (double) i));
} else {
g_object_get (T_DOUBLE (other), "value", &d, NULL);
if (d == 0) {
g_signal_emit_by_name (self, "div-by-zero");
return NULL;
} else
return T_NUMBER (t_double_new_with_value (T_DOUBLE(self)->value / d));
}
}
static TNumber *
t_double_uminus (TNumber *self) {
g_return_val_if_fail (T_IS_DOUBLE (self), NULL);
return T_NUMBER (t_double_new_with_value (- T_DOUBLE(self)->value));
}
static char *
t_double_to_s (TNumber *self) {
g_return_val_if_fail (T_IS_DOUBLE (self), NULL);
double d;
g_object_get (T_DOUBLE (self), "value", &d, NULL);
return g_strdup_printf ("%lf", d);
}
static void
t_double_class_init (TDoubleClass *class) {
TNumberClass *tnumber_class = T_NUMBER_CLASS (class);
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
/* override virtual functions */
tnumber_class->add = t_double_add;
tnumber_class->sub = t_double_sub;
tnumber_class->mul = t_double_mul;
tnumber_class->div = t_double_div;
tnumber_class->uminus = t_double_uminus;
tnumber_class->to_s = t_double_to_s;
gobject_class->set_property = t_double_set_property;
gobject_class->get_property = t_double_get_property;
double_property = g_param_spec_double ("value", "val", "Double value", -G_MAXDOUBLE, G_MAXDOUBLE, 0, G_PARAM_READWRITE);
g_object_class_install_property (gobject_class, PROP_DOUBLE, double_property);
}
TDouble *
t_double_new_with_value (double value) {
TDouble *d;
d = g_object_new (T_TYPE_DOUBLE, "value", value, NULL);
return d;
}
TDouble *
t_double_new (void) {
TDouble *d;
d = g_object_new (T_TYPE_DOUBLE, NULL);
return d;
}
main.c
is a simple program to test the objects.
#include <glib-object.h>
#include "tnumber.h"
#include "tint.h"
#include "tdouble.h"
static void
notify_cb (GObject *gobject, GParamSpec *pspec, gpointer user_data) {
const char *name;
int i;
double d;
name = g_param_spec_get_name (pspec);
if (T_IS_INT (gobject) && strcmp (name, "value") == 0) {
g_object_get (T_INT (gobject), "value", &i, NULL);
g_print ("Property \"%s\" is set to %d.\n", name, i);
} else if (T_IS_DOUBLE (gobject) && strcmp (name, "value") == 0) {
g_object_get (T_DOUBLE (gobject), "value", &d, NULL);
g_print ("Property \"%s\" is set to %lf.\n", name, d);
}
}
int
main (int argc, char **argv) {
TNumber *i, *d, *num;
char *si, *sd, *snum;
i = T_NUMBER (t_int_new ());
d = T_NUMBER (t_double_new ());
g_signal_connect (G_OBJECT (i), "notify::value", G_CALLBACK (notify_cb), NULL);
g_signal_connect (G_OBJECT (d), "notify::value", G_CALLBACK (notify_cb), NULL);
g_object_set (T_INT (i), "value", 100, NULL);
g_object_set (T_DOUBLE (d), "value", 12.345, NULL);
num = t_number_add (i, d);
si = t_number_to_s (i);
sd = t_number_to_s (d);
snum = t_number_to_s (num);
g_print ("%s + %s is %s.\n", si, sd, snum);
g_object_unref (num);
g_free (snum);
num = t_number_add (d, i);
snum = t_number_to_s (num);
g_print ("%s + %s is %s.\n", sd, si, snum);
g_object_unref (num);
g_free (sd);
g_free (snum);
g_object_set (T_DOUBLE (d), "value", 0.0, NULL);
sd = t_number_to_s (d);
if ((num = t_number_div(i, d)) != NULL) {
snum = t_number_to_s (num);
g_print ("%s / %s is %s.\n", si, sd, snum);
g_object_unref (num);
g_free (snum);
}
g_object_unref (i);
g_object_unref (d);
g_free (si);
g_free (sd);
return 0;
}
main
.i
(TInt) and
d
(TDouble).i
and
d
.d
to i
. The answer is TInt
object.i
to d
. The answer is TDouble
object. The addition of two TNumber objects isn’t commutative because
the type of the result will be different if the two objects are
exchanged.The source files are located under src/tnumber. The file
meson.buld
, which controls the compilation process, is as
follows.
project ('tnumber', 'c')
gobjdep = dependency('gobject-2.0')
sourcefiles = files('main.c', 'tnumber.c', 'tint.c', 'tdouble.c')
executable('tnumber', sourcefiles, dependencies: gobjdep, install: false)
Compilation and execution is done by the usual way.
$ cd src/tnumber
$ meson setup _build
$ ninja -C _build
$ _build/tnumber
Then the following is shown on the display.
Property "value" is set to 100.
Property "value" is set to 12.345000.
100 + 12.345000 is 112.
12.345000 + 100 is 112.345000.
Property "value" is set to 0.000000.
Error: division by zero.
The two answers are different because of the different types.
This section has shown a simple example of derivable and abstract
class. You can define your derivable object like this. If your object
isn’t abstract, use G_DEFINE_TYPE
instead of
G_DEFINE_ABSTRACT_TYPE
. And you need one more thing, how to
manage private data in your derivable object. There is a tutorial in GObject
API Reference. See the tutorial for learning derivable object.
It is also good to see source files in GTK.
Because TNumber is an abstract object, you cannot instantiate it
directly. And you cannot create the TNumber class as well. But when you
create its descendant instance, TNumber class is made and initialized.
First call for g_object_new (T_TYPE_INT, ...)
or
g_object_new (T_TYPE_DOUBLE, ...)
creates and initializes
TNumberClass if the class doesn’t exist. After that, TIntClass or
TDoubleClass is created and followed by the creation for TInt or TDouble
instance respectively.
And the initialization process for the TNumber class is as follows.
main
starts.t_number_class_init
is called. It initializes class methods (Pointers to the class methods)
and a default signal handler.The diagram below shows the process.
g_object_new (T_TYPE_INT, ...)
initializes TIntClass. And the initialization process is as
follows.t_int_class_init
is
called. It overrides class methods from TNumber,
set_property
and get_property
.The diagram below shows the process.