Derivable type and abstract type

Derivable type

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 ())
G_DECLARE_DERIVABLE_TYPE (TNumber, t_number, T, NUMBER, GObject)

Abstract 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 class

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);

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;
}

TInt object.

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);

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;
}

TDouble object.

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

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;
}

Compilation and execution

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.

Class initialization process

Initialization process of TNumberClass

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.

  1. GObjectClass has been initialized before the function main starts.
  2. Memory is allocated for TNumberClass.
  3. The parent (GObjectClass) part of the class is copied from GObjectClass.
  4. The class initialization function 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.

TNumberClass initialization

Initialization process of TIntClass

  1. TNumberClass has been initialized before the initialization of TIntClass starts.
  2. First call for g_object_new (T_TYPE_INT, ...) initializes TIntClass. And the initialization process is as follows.
  3. Memory is allocated for TIntClass. TIntClass doesn’t have its own area. Therefore its structure is the same as its parent class (TNumberClass).
  4. The parent (TNumberClass) part of the class (This is the same as whole TIntClass) is copied from TNumberClass.
  5. The class initialization function t_int_class_init is called. It overrides class methods from TNumber, set_property and get_property.

The diagram below shows the process.

TIntClass initialization