Instantiable classed types: objects

Types which are registered with a class and are declared instantiable are what most closely resembles an object. Although GObjects (detailed in The GObject base class) are the most well known type of instantiable classed types, other kinds of similar objects used as the base of an inheritance hierarchy have been externally developped and they are all built on the fundamental features described below.

For example, the code below shows how you could register such a fundamental object type in the type system:

typedef struct {
  GObject parent;
  /* instance members */
  int field_a;
} MamanBar;

typedef struct {
  GObjectClass parent;
  /* class members */
  void (*do_action_public_virtual) (MamanBar *self, guint8 i);

  void (*do_action_public_pure_virtual) (MamanBar *self, guint8 i);
} MamanBarClass;

#define MAMAN_BAR_TYPE (maman_bar_get_type ())

GType 
maman_bar_get_type (void)
{
  static GType type = 0;
  if (type == 0) {
    static const GTypeInfo info = {
      sizeof (MamanBarClass),
      NULL,           /* base_init */
      NULL,           /* base_finalize */
      (GClassInitFunc) foo_class_init,
      NULL,           /* class_finalize */
      NULL,           /* class_data */
      sizeof (MamanBar),
      0,              /* n_preallocs */
      (GInstanceInitFunc) NULL /* instance_init */
    };
    type = g_type_register_static (G_TYPE_OBJECT,
                                   "BarType",
                                   &info, 0);
  }
  return type;
}

Upon the first call to maman_bar_get_type, the type named BarType will be registered in the type system as inheriting from the type G_TYPE_OBJECT.

Every object must define two structures: its class structure and its instance structure. All class structures must contain as first member a GTypeClass structure. All instance structures must contain as first member a GTypeInstance structure. The declaration of these C types, coming from gtype.h is shown below:

struct _GTypeClass
{
  GType g_type;
};
struct _GTypeInstance
{
  GTypeClass *g_class;
};

These constraints allow the type system to make sure that every object instance (identified by a pointer to the object's instance structure) contains in its first bytes a pointer to the object's class structure.

This relationship is best explained by an example: let's take object B which inherits from object A:

/* A definitions */
typedef struct {
  GTypeInstance parent;
  int field_a;
  int field_b;
} A;
typedef struct {
  GTypeClass parent_class;
  void (*method_a) (void);
  void (*method_b) (void);
} AClass;

/* B definitions. */
typedef struct {
  A parent;
  int field_c;
  int field_d;
} B;
typedef struct {
  AClass parent_class;
  void (*method_c) (void);
  void (*method_d) (void);
} BClass;

The C standard mandates that the first field of a C structure is stored starting in the first byte of the buffer used to hold the structure's fields in memory. This means that the first field of an instance of an object B is A's first field which in turn is GTypeInstance's first field which in turn is g_class, a pointer to B's class structure.

Thanks to these simple conditions, it is possible to detect the type of every object instance by doing:

B *b;
b->parent.parent.g_class->g_type

or, more quickly:

B *b;
((GTypeInstance*)b)->g_class->g_type

Initialization and Destruction

Instanciation of these types can be done with g_type_create_instance:

GTypeInstance* g_type_create_instance (GType          type);
void           g_type_free_instance   (GTypeInstance *instance);

g_type_create_instance will lookup the type information structure associated to the type requested. Then, the instance size and instanciation policy (if the n_preallocs field is set to a non-zero value, the type system allocates the object's instance structures in chunks rather than mallocing for every instance) declared by the user are used to get a buffer to hold the object's instance structure.

If this is the first instance of the object ever created, the type system must create a class structure: it allocates a buffer to hold the object's class structure and initializes it. It first copies the parent's class structure over this structure (if there is no parent, it initializes it to zero). It then invokes the base_class_initialization functions (GBaseInitFunc) from topmost fundamental object to bottom-most most derived object. The object's class_init (GClassInitFunc) function is invoked afterwards to complete initialization of the class structure. Finally, the object's interfaces are initialized (we will discuss interface initialization in more detail later). [4]

Once the type system has a pointer to an initialized class structure, it sets the object's instance class pointer to the object's class structure and invokes the object's instance_init (GInstanceInitFunc)functions, from top-most fundamental type to bottom-most most derived type.

Object instance destruction through g_type_free_instance is very simple: the instance structure is returned to the instance pool if there is one and if this was the last living instance of the object, the class is destroyed.

Class destruction [5] (the concept of destruction is sometimes partly refered to as finalization in GType) is the symmetric process of the initialization: interfaces are destroyed first. Then, the most derived class_finalize (ClassFinalizeFunc) function is invoked. The base_class_finalize (GBaseFinalizeFunc) functions are Finally invoked from bottom-most most-derived type to top-most fundamental type and the class structure is freed.

As many readers have now understood it, the base initialization/finalization process is very similar to the C++ Constructor/Destructor paradigm. The practical details are quite different though and it is important not to get confused by the superficial similarities. Typically, what most users have grown to know as a C++ constructor (that is, a list of object methods invoked on the object instance once for each type of the inheritance hierachy) does not exist in GType and must be built on top of the facilities offered by GType. Similarly, GTypes have no instance destruction mechanism. It is the user's responsibility to implement correct destruction semantics on top of the existing GType code. (this is what GObject does. See The GObject base class)

For example, if the object B which derives from A is instantiated, GType will only invoke the instance_init callback of object B while a C++ runtime will invoke the constructor of the object type A first and then of the object type B. Furthermore, the C++ code equivalent to the base_init and class_init callbacks of GType is usually not needed because C++ cannot really create object types at runtime.

The instanciation/finalization process can be summarized as follows:

Table 1. GType Instantiation/Finalization

Invocation time Function Invoked Function's parameters
First call to g_type_create_instance for target type type's base_init function On the inheritance tree of classes from fundamental type to target type. base_init is invoked once for each class structure.
target type's class_init function On target type's class structure
interface initialization, see the section called “Interface Initialization”  
Each call to g_type_create_instance for target type target type's instance_init function On object's instance
Last call to g_type_free_instance for target type interface destruction, see the section called “Interface Destruction”  
target type's class_finalize function On target type's class structure
type's base_finalize function On the inheritance tree of classes from fundamental type to target type. base_finalize is invoked once for each class structure.




[4] The class initialization process is entirely implemented in type_class_init_Wm in gtype.c.

[5] It is implemented in type_data_finalize_class_U (in gtype.c.