Interface

Interface is similar to abstract class. Interface defines virtual functions which are expected to be overridden by a function in another instantiable object.

This section provides a simple example, TComparable. TComparable is an interface. It defines functions to compare. They are:

Numbers and strings are comparable. TInt, TDouble and TStr implement TComparable interface so that they can use the functions above. In addition, TNumStr can use the functions because it is a child class of TStr.

For example,

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 */

What’s the difference between interface and abstract class? Virtual functions in an abstract class are overridden by a function in its descendant classes. Virtual functions in an interface are overridden by a function in any classes. Compare TNumber and TComparable.

TComparable interface

Defining interfaces is similar to defining objects.

Now let’s see the header file.

#pragma once

#include <glib-object.h>

#define T_TYPE_COMPARABLE  (t_comparable_get_type ())
G_DECLARE_INTERFACE (TComparable, t_comparable, T, COMPARABLE, GObject)

struct _TComparableInterface {
  GTypeInterface parent;
  /* signal */
  void (*arg_error) (TComparable *self);
  /* virtual function */
  int (*cmp) (TComparable *self, TComparable *other);
};

/* t_comparable_cmp */
/* if self > other, then returns 1 */
/* if self = other, then returns 0 */
/* if self < other, then returns -1 */
/* if error happens, then returns -2 */

int
t_comparable_cmp (TComparable *self, TComparable *other);

gboolean
t_comparable_eq (TComparable *self, TComparable *other);

gboolean
t_comparable_gt (TComparable *self, TComparable *other);

gboolean
t_comparable_lt (TComparable *self, TComparable *other);

gboolean
t_comparable_ge (TComparable *self, TComparable *other);

gboolean
t_comparable_le (TComparable *self, TComparable *other);

C file tcomparable.c is as follows.

#include "tcomparable.h"

static guint t_comparable_signal;

G_DEFINE_INTERFACE (TComparable, t_comparable, G_TYPE_OBJECT)

static void
arg_error_default_cb (TComparable *self) {
  g_printerr ("\nTComparable: argument error.\n");
}

static void
t_comparable_default_init (TComparableInterface *iface) {
  /* virtual function */
  iface->cmp = NULL;
  /* argument error signal */
  iface->arg_error = arg_error_default_cb;
  t_comparable_signal =
  g_signal_new ("arg-error",
                T_TYPE_COMPARABLE,
                G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
                G_STRUCT_OFFSET (TComparableInterface, arg_error),
                NULL /* accumulator */,
                NULL /* accumulator data */,
                NULL /* C marshaller */,
                G_TYPE_NONE /* return_type */,
                0     /* n_params */
                );
}

int
t_comparable_cmp (TComparable *self, TComparable *other) {
  g_return_val_if_fail (T_IS_COMPARABLE (self), -2);

  TComparableInterface *iface = T_COMPARABLE_GET_IFACE (self);
  
  return (iface->cmp == NULL ? -2 : iface->cmp (self, other));
}

gboolean
t_comparable_eq (TComparable *self, TComparable *other) {
  return (t_comparable_cmp (self, other) == 0);
}

gboolean
t_comparable_gt (TComparable *self, TComparable *other) {
  return (t_comparable_cmp (self, other) == 1);
}

gboolean
t_comparable_lt (TComparable *self, TComparable *other) {
  return (t_comparable_cmp (self, other) == -1);
}

gboolean
t_comparable_ge (TComparable *self, TComparable *other) {
  int result = t_comparable_cmp (self, other);
  return (result == 1 || result == 0);
}

gboolean
t_comparable_le (TComparable *self, TComparable *other) {
  int result = t_comparable_cmp (self, other);
  return (result == -1 || result == 0);
}

This program uses a signal to give the argument type error information to a user. This error is usually a program error rather than a run-time error. Using a signal to report a program error is not a good way. The best way is using g_return_if_fail. The reason why I made this signal is just I wanted to show how to implement a signal in interfaces.

Implementing interface

TInt, TDouble and TStr implement TComparable. First, look at TInt. The header file is the same as before. The implementation is written in C file.

tint.c is as follows.

#include "../tnumber/tnumber.h"
#include "../tnumber/tint.h"
#include "../tnumber/tdouble.h"
#include "tcomparable.h"

enum {
  PROP_0,
  PROP_INT,
  N_PROPERTIES
};

static GParamSpec *int_properties[N_PROPERTIES] = {NULL, };

struct _TInt {
  TNumber parent;
  int value;
};

static void t_comparable_interface_init (TComparableInterface *iface);

G_DEFINE_TYPE_WITH_CODE (TInt, t_int, T_TYPE_NUMBER,
                         G_IMPLEMENT_INTERFACE (T_TYPE_COMPARABLE, t_comparable_interface_init))

static int
t_int_comparable_cmp (TComparable *self, TComparable *other) {
  if (! T_IS_NUMBER (other)) {
    g_signal_emit_by_name (self, "arg-error");
    return -2;
  }

  int i;
  double s, o;

  s = (double) T_INT (self)->value;
  if (T_IS_INT (other)) {
    g_object_get (other, "value", &i, NULL);
    o = (double) i;
  } else
    g_object_get (other, "value", &o, NULL);
  if (s > o)
    return 1;
  else if (s == o)
    return 0;
  else if (s < o)
    return -1;
  else /* This can't happen. */
    return -2;
}

static void
t_comparable_interface_init (TComparableInterface *iface) {
  iface->cmp = t_int_comparable_cmp;
}

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_properties[PROP_INT] = g_param_spec_int ("value", "val", "Integer value", G_MININT, G_MAXINT, 0, G_PARAM_READWRITE);
  g_object_class_install_properties (gobject_class, N_PROPERTIES, int_properties);
}

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.c is almost same as tint.c. These two objects can be compared because int is casted to double before the comparison.

tstr.c is as follows.

#include "../tstr/tstr.h"
#include "tcomparable.h"

enum {
  PROP_0,
  PROP_STRING,
  N_PROPERTIES
};

static GParamSpec *str_properties[N_PROPERTIES] = {NULL, };

typedef struct {
  char *string;
} TStrPrivate;

static void t_comparable_interface_init (TComparableInterface *iface);

G_DEFINE_TYPE_WITH_CODE (TStr, t_str, G_TYPE_OBJECT,
                         G_ADD_PRIVATE (TStr)
                         G_IMPLEMENT_INTERFACE (T_TYPE_COMPARABLE, t_comparable_interface_init))

static void
t_str_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) {
  TStr *self = T_STR (object);

/* The returned value of the function g_value_get_string can be NULL. */
/* The function t_str_set_string calls a class method, */
/* which is expected to rewrite in the descendant object. */
  if (property_id == PROP_STRING)
    t_str_set_string (self, g_value_get_string (value));
  else
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
t_str_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) {
  TStr *self = T_STR (object);
  TStrPrivate *priv = t_str_get_instance_private (self);

/* The second argument of the function g_value_set_string can be NULL. */
  if (property_id == PROP_STRING)
    g_value_set_string (value, priv->string);
  else
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

/* This function just set the string. */
/* So, no notify signal is emitted. */
static void
t_str_real_set_string (TStr *self, const char *s) {
  TStrPrivate *priv = t_str_get_instance_private (self);

  if (priv->string)
    g_free (priv->string);
  priv->string = g_strdup (s);
}

static void
t_str_finalize (GObject *object) {
  TStr *self = T_STR (object);
  TStrPrivate *priv = t_str_get_instance_private (self);

  if (priv->string)
    g_free (priv->string);
  G_OBJECT_CLASS (t_str_parent_class)->finalize (object);
}

static int
t_str_comparable_cmp (TComparable *self, TComparable *other) {
  if (! T_IS_STR (other)) {
    g_signal_emit_by_name (self, "arg-error");
    return -2;
  }

  char *s, *o;
  int result;

  s = t_str_get_string (T_STR (self));
  o = t_str_get_string (T_STR (other));

  if (strcmp (s, o) > 0)
    result = 1;
  else if (strcmp (s, o) == 0)
    result = 0;
  else if (strcmp (s, o) < 0)
    result = -1;
  else /* This can't happen. */
    result = -2;
  g_free (s);
  g_free (o);
  return result;
}

static void
t_comparable_interface_init (TComparableInterface *iface) {
  iface->cmp = t_str_comparable_cmp;
}

static void
t_str_init (TStr *self) {
  TStrPrivate *priv = t_str_get_instance_private (self);

  priv->string = NULL;
}

static void
t_str_class_init (TStrClass *class) {
  GObjectClass *gobject_class = G_OBJECT_CLASS (class);

  gobject_class->finalize = t_str_finalize;
  gobject_class->set_property = t_str_set_property;
  gobject_class->get_property = t_str_get_property;
  str_properties[PROP_STRING] = g_param_spec_string ("string", "str", "string", "", G_PARAM_READWRITE);
  g_object_class_install_properties (gobject_class, N_PROPERTIES, str_properties);

  class->set_string = t_str_real_set_string;
}

/* setter and getter */
void
t_str_set_string (TStr *self, const char *s) {
  g_return_if_fail (T_IS_STR (self));
  TStrClass *class = T_STR_GET_CLASS (self);

/* The setter calls the class method 'set_string', */
/* which is expected to be overridden by the descendant TNumStr. */
/* Therefore, the behavior of the setter is different between TStr and TNumStr. */
  class->set_string (self, s);
}

char *
t_str_get_string (TStr *self) {
  g_return_val_if_fail (T_IS_STR (self), NULL);
  TStrPrivate *priv = t_str_get_instance_private (self);

  return g_strdup (priv->string);
}

TStr *
t_str_concat (TStr *self, TStr *other) {
  g_return_val_if_fail (T_IS_STR (self), NULL);
  g_return_val_if_fail (T_IS_STR (other), NULL);

  char *s1, *s2, *s3;
  TStr *str;

  s1 = t_str_get_string (self);
  s2 = t_str_get_string (other);
  if (s1 && s2)
    s3 = g_strconcat (s1, s2, NULL);
  else if (s1)
    s3 = g_strdup (s1);
  else if (s2)
    s3 = g_strdup (s2);
  else
    s3 = NULL;
  str = t_str_new_with_string (s3);
  if (s1) g_free (s1);
  if (s2) g_free (s2);
  if (s3) g_free (s3);
  return str;
}

/* create a new TStr instance */
TStr *
t_str_new_with_string (const char *s) {
  return T_STR (g_object_new (T_TYPE_STR, "string", s, NULL));
}

TStr *
t_str_new (void) {
  return T_STR (g_object_new (T_TYPE_STR, NULL));
}

TStr can be compared with TStr, but not with TInt nor TDouble. Generally, comparison is available between two same type instances.

TNumStr itself doesn’t implement TComparable. But it is a child of TStr, so it is comparable. The comparison is based on the alphabetical order. So, “a” is bigger than “b” and “three” is bigger than “two”.

Test program.

main.c is a test program.

#include <glib-object.h>
#include "tcomparable.h"
#include "../tnumber/tnumber.h"
#include "../tnumber/tint.h"
#include "../tnumber/tdouble.h"
#include "../tstr/tstr.h"

static void
t_print (const char *cmp, TComparable *c1, TComparable *c2) {
  char *s1, *s2;
  TStr *ts1, *ts2, *ts3;

  ts1 = t_str_new_with_string("\"");
  if (T_IS_NUMBER (c1))
    s1 = t_number_to_s (T_NUMBER (c1));
  else if (T_IS_STR (c1)) {
    ts2 = t_str_concat (ts1, T_STR (c1));
    ts3 = t_str_concat (ts2, ts1);
    s1 = t_str_get_string (T_STR (ts3));
    g_object_unref (ts2);
    g_object_unref (ts3);
  } else {
    g_print ("c1 isn't TInt, TDouble nor TStr.\n");
    return;
  }
  if (T_IS_NUMBER (c2))
    s2 = t_number_to_s (T_NUMBER (c2));
  else if (T_IS_STR (c2)) {
    ts2 = t_str_concat (ts1, T_STR (c2));
    ts3 = t_str_concat (ts2, ts1);
    s2 = t_str_get_string (T_STR (ts3));
    g_object_unref (ts2);
    g_object_unref (ts3);
  } else {
    g_print ("c2 isn't TInt, TDouble nor TStr.\n");
    return;
  }
  g_print ("%s %s %s.\n", s1, cmp, s2);
  g_object_unref (ts1);
  g_free (s1);
  g_free (s2);
}    

static void
compare (TComparable *c1, TComparable *c2) {
  if (t_comparable_eq (c1, c2))
    t_print ("equals", c1, c2);
  else if (t_comparable_gt (c1, c2))
    t_print ("is greater than", c1, c2);
  else if (t_comparable_lt (c1, c2))
    t_print ("is less than", c1, c2);
  else if (t_comparable_ge (c1, c2))
    t_print ("is greater than or equal to", c1, c2);
  else if (t_comparable_le (c1, c2))
    t_print ("is less than or equal to", c1, c2);
  else
    t_print ("can't compare to", c1, c2);
}

int
main (int argc, char **argv) {
  const char *one = "one";
  const char *two = "two";
  const char *three = "three";
  TInt *i;
  TDouble *d;
  TStr *str1, *str2, *str3;

  i = t_int_new_with_value (124);
  d = t_double_new_with_value (123.45);
  str1 = t_str_new_with_string (one);
  str2 = t_str_new_with_string (two);
  str3 = t_str_new_with_string (three);

  compare (T_COMPARABLE (i), T_COMPARABLE (d));
  compare (T_COMPARABLE (str1), T_COMPARABLE (str2));
  compare (T_COMPARABLE (str2), T_COMPARABLE (str3));
  compare (T_COMPARABLE (i), T_COMPARABLE (str1));

  g_object_unref (i);
  g_object_unref (d);
  g_object_unref (str1);
  g_object_unref (str2);
  g_object_unref (str3);

  return 0;
}

Compilation and execution

Change your current directory to src/tcomparable.

$ cd src/tcomparable
$ meson setup _build
$ ninja -C _build

Then execute it.

$ _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".

Build an interface without macros

We used macros such as G_DECLARE_INTERFACE, G_DEFINE_INTERFACE to build an interface. And G_DEFINE_TYPE_WITH_CODE to implement the interface. We can also build it without macros. There are three files in the tcomparable directory.

They don’t use macros. Instead, they register the interface or implementation of the interface to the type system directly. If you want to know that, see the source files in src/tcomparable.

GObject system and object oriented languages

If you know any object oriented languages, you probably have thought that GObject and the languages are similar. Learning such languages is very useful to know GObject system.