Derivable and non-abstract type

It is more common to make a non-abstract derivable type than abstract type. This section covers how to make non-abstract derivable type objects. A derivable type example is an object for string. It is TStr. And its child is an object for numeric string. A numeric string is a string that expresses a number. For example, “0”, “-100” and “123.45”. The child object (numeric string) will be explained in the next section.

This section describes memory management for strings before derivable objects.

String and memory management

TStr has a string type value. It is similar to TInt or TDouble but string is more complex than int and double. When you make TStr program, you need to be careful about memory management, which is not necessary to TInt and TDouble.

String and memory

String is an array of characters that is terminated with ‘\0’. String is not a C type such as char, int, float or double. But the pointer to a character array behaves like a string type of other languages. So, we often call the pointer string.

If the pointer is NULL, it points nothing. So, the pointer is not a string. Programs with string will include bugs if you aren’t careful about NULL pointer.

Another annoying problem is memory allocation. Because string is an array of characters, memory allocation is necessary to create a new string. We don’t forget to allocate memory, but often forget to free the memory. It causes memory leak.

char *s;
s = g_strdup ("Hello.");
... ... ... do something with s
g_free (s);

g_strdup duplicates a string. It does:

If the string s is no longer in use, s must be freed, which means the allocated 7 bytes must be returned to the system. g_free frees the memory.

Strings bounded by double quotes like “Hello.” are string literals. They are an array of characters, but the contents of the array are not allowed to change. And they mustn’t be freed. If you write a character in a string literal or free a string literal, the result is undefined. Maybe bad things will happen, for example, a segmentation fault error.

There’s a difference between arrays and pointers when you initialize them with a string literal. If an array is initialized with a string literal, the array can be changed.

char a[]="Hello!";
a[1]='a';
g_print ("%s\n", a); /* Hallo will appear on your display. */

The first line initializes an array a. The initialization is not simple. First, the compiler calculates the length of “Hello!”. It is seven because the string literal has ‘\0’ at the end of it. Then seven bytes memory is allocated in static memory or stack memory. It depends on the class of the array, whether static or auto. The memory is initialized with “Hello!”. So, the string in the array can be changed. This program successfully displays `Hallo!.

The first line of the program above is the same as follows.

char a[] = {'H', 'e', 'l', 'l', 'o', '!', '\0'};

If you define a pointer with string literal, you can’t change the string pointed by the pointer.

char *a = "Hello";
*(a+1) = 'a'; /* This is illegal. */
g_print ("%s\n", a);

The first line just assigns the address of the string literal to the variable a. String literal is an array of characters but it’s read-only. It might be in the program code area or some other non-writable area. It depends on the implementation of your compiler. Therefore, the second line tries to write a char ‘a’ to the read-only memory and the result is undefined, for example, a segmentation error happens. Anyway, don’t write a program like this.

In conclusion, a string is an array of characters and it is placed in one of the following.

Copying string

There are two ways to copy a string. First way is just copying the pointer.

char *s = "Hello";
char *t = s;

Two pointers s and t points the same address. Therefore, you can’t modify t because t points a string literal, which is read-only.

Second way is creating memory and copying the string to the memory.

char *s = "Hello";
char *t = g_strdup (s);

The function g_strdup allocates memory and initializes it with “Hello”, then returns the pointer to the memory. The function g_strdup is almost same as the function string_dup below.

#include <glib-object.h>
#include <string.h>

char *
string_dup (char *s) {
  int length;
  char *t;

  if (s == NULL)
    return NULL;
  length = strlen (s) + 1;
  t = g_new (char, length);
  strcpy (t, s);
  return t;
}

If g_strdup is used, the two pointers s and t point different memories. You can modify t because it is placed in the memory allocated from the heap area.

It is important to know the difference between assigning pointers and duplicating strings.

const qualifier

The qualifier const makes a variable won’t change its value. It can also be applied to an array. Then, the elements of the array won’t be changed.

const double pi = 3.1415926536;
const char a[] = "read only string";

An array parameter in a function can be qualified with const to indicate that the function does not change the array. In the same way, the return value (a pointer to an array or string) of a function can be qualified with const. The caller mustn’t modify or free the returned array or string.

char *
string_dup (const char *s) {
  ... ...
}

const char *
g_value_get_string (const GValue *value);

The qualifier const indicates who is the owner of the string when it is used in the function of objects. “Owner” is an object or a caller of the function that has the right to modify or free the string.

For example, g_value_get_string is given const GValue *value as an argument. The GValue pointed by value is owned by the caller and the function doesn’t change or free it. The function returns a string qualified with const. The returned string is owned by the object and the caller mustn’t change or free the string. It is possible that the string will be changed or freed by the object later.

Header file

The rest of this section is about TStr. TStr is a child of GObject and it holds a string. The string is a pointer and an array of characters. The pointer points the array. The pointer can be NULL. If it is NULL, TStr has no array. The memory of the array comes from the heap area. TStr owns the memory and is responsible to free it when it becomes useless. TStr has a string type property.

The header file tstr.h is as follows.

#pragma once

#include <glib-object.h>

#define T_TYPE_STR  (t_str_get_type ())
G_DECLARE_DERIVABLE_TYPE (TStr, t_str, T, STR, GObject)

struct _TStrClass {
  GObjectClass parent_class;
  /* expect that descendants will override the setter */
  void (*set_string)  (TStr *self, const char *s);
};

TStr *
t_str_concat (TStr *self, TStr *other);

/* setter and getter */
void
t_str_set_string (TStr *self, const char *s);

char *
t_str_get_string (TStr *self);

/* create a new TStr instance */
TStr *
t_str_new_with_string (const char *s);

TStr *
t_str_new (void);

C file

The C file tstr.c for TStr is as follows.

#include "tstr.h"

enum {
  PROP_0,
  PROP_STRING,
  N_PROPERTIES
};

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

typedef struct {
  char *string;
} TStrPrivate;

G_DEFINE_TYPE_WITH_PRIVATE(TStr, t_str, G_TYPE_OBJECT)

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

Chaining up to its parent

The “chain up to its parent” process is illustrated with the diagram below.

Chaining up process in GObject and TStr

There are two classes, GObjectClass and TStrClass. Each class has their finalize methods (functions) pointed by the pointers in the class structures. The finalize method of TStrClass finalizes its own part of the TStr instance. At the end of the function, it calls its parent’s finalize method. It is the finalize method of GObjectClass. It calls its own finalize function and finalizes the GObject private data.

If the GObjectClass has two or more descendant classes, the number of the finalize functions may be the same as the number of the descendants. And they are connected by “chain up to its parent” way.

Chaining up process

How to write a derivable type