Previously: Writing Object-Oriented Code with GLib/GObject
In general, the code follows the examples and guidelines provided in the online documentation for GTK+. However, you may notice certain deviations in cases where I thought better of doing as the GTK+ developers do. In particular:
- No use is made of GObject properties, as this tends to add an extra layer of complexity to code for very little return. Accessor and mutator methods serve better, and are generally faster because they do not require the lookup time to match a property name to the method that operates on it; nor again do they require the time taken to cascade through a series of
case
statements until the right match is found. It is true that we lose blurbs and descriptions for class properties -- but, of course, to document those is one of the reasons for project documentation. We also lose construction properties, but class factory methods easily overcome this loss. - Type identifiers are named
CLASS_NAME_TYPE
instead ofTYPE_CLASS_NAME
; e.g.,AN_OBJECT_TYPE
instead ofA_TYPE_OBJECT
. This is purely for aesthetic reasons, as I feel that the former usage "sounds" better than the latter usage.
Defining a Class
Using glib-utility.h
, one defines a class as follows:
#include "glib-utility.h"
// The base class, from which all other classes are derived
g_class(MamanBar, GObject, MAMAN_BAR, maman_bar,
g_instance(
/* Instance members */
/* <private> */
int hsize;
),
/* Class members */
)
// Type identity for MamanBar
#define MAMAN_BAR_TYPE (maman_bar_get_type())
If you compare the above to the example provided on the GTK+ website, you will see how much more streamlined glib-utility.h
can make things.
So how does it work? The parameters expected by the g_class()
macro are:
- The base name of the class; i.e.,
MamanBar
. Note that we are not providing the name of the class structure; merely the name of the class as it is referenced by your documentation or by other code. - The base name of the parent class; i.e.,
GObject
. Once again, we are not providing the name of the class structure, only the name of the class itself. In the GTK+ documentation,GObject
is always referred to as such, never asGObjectClass
. - The type name of the class. This is the name used to defined the type identity of the class, as well as the support macros: e.g.,
IS_MAMAN_BAR()
,IS_MAMAN_BAR_CLASS()
, and so forth. - The prefix used for all methods pertaining to the class or its instances; i.e.,
maman_bar
, as inmaman_bar_get_type()
. - Instance members, prefixed by the
g_instance()
macro. - Class members.
g_class()
expands into several statements, which:
- Define and implement the type support macros. These are all defined and implemented as static inline functions, since C preprocessor macros cannot themselves define C preprocessor macros. In GCC, inline functions are fast as macros.
MAMAN_BAR()
: Used to cast arbitrary instances to instances ofMamanBar
.IS_MAMAN_BAR()
: Used to determine if an arbitrary instance is an instance ofMamanBar
or its descendants.MAMAN_BAR_CLASS()
: Used to cast an arbitrary class structure reference to one ofMamanBarClass
.IS_MAMAN_BAR_CLASS()
: Used to determine whether an arbitrary class structure reference is related toMamanBarClass
or its descendants.MAMAN_BAR_GET_CLASS()
: Used to retrieve a reference toMamanBarClass
from an arbitrary instance ofMamanBar
or its descendants.
- Perform the type definitions of
MamanBar
andMamanBarClass
. - Begin the definition of
struct MamanBar
, including the definition of its first member,Parent
, which is of typeGObject
. - The
g_instance()
macro then takes over, allowing the specification of additional instance members. It also definesstruct MamanBarClass
and its first member,Parent
, which is of typeGObjectClass
, so that when you continue the class definition outside ofg_instance()
, the members are assigned toMamanBarClass
.
All of this is done behind the scenes and with far fewer keystrokes.
Implementing a Class
One implements a class using glib-utility.h
as follows:
#include "maman-bar.h"
/* For concise code, all class methods should be implemented
BEFORE g_class_implement() is used. However, it is also
possible to declare the prototypes of these methods here,
then use g_class_implement(), and THEN provide the actual
implementation of class methods.
*/
g_class_implement(MamanBar, G_TYPE_OBJECT, MAMAN_BAR, maman_bar,
g_class_preamble(),
g_class_typedef(
/* If your class implements any interfaces, you will declare
that here.
*/
),
g_class_init(
/* Class structure initialization goes here. This area
is primarily used to define (and override) virtual
method handlers.
*/
),
g_instance_init(
/* Instance initialization goes here. You will likely
use this area only to initialize instance variables.
*/
Self->hsize = 0;
),
g_instance_dispose(),
g_instance_finalize()
)
Once again, compare the above to the example provided on the GTK+ documentation website.
How does it work? g_class_implment()
expects the following parameters:
- The name of the class being implemented; i.e.,
MamanBar
. As withg_class()
, we never use the name of the class structure. - The type identity of the parent class; i.e.,
G_TYPE_OBJECT
. - The type name of the class being implemented; as with
g_class()
, this is the name of the class as it is used in the type identity and type helper macros:MAMAN_BAR
. - The prefix used for methods related to the class, as in
maman_bar_get_type()
:maman_bar
. - Preamble code, which is placed immediately before
maman_bar_get_type()
is implemented. See below for more information. - Class structure construction code.
- Class instance construction code.
- Class instance destruction code.
- Class instance finalization code.
g_class_implement()
expands into several statements, which:
- Implement the type identity function:
maman_bar_get_type()
. - Define the boilerplate construction and destruction routines. This is where
g_class_preamble()
,g_class_typedef()
,g_class_init()
,g_instance_init()
,g_instance_dispose()
, andg_instance_finalize()
are used.- You will likely never use
g_class_preamble()
, except to declare it, since its purpose is to allow you to insert custom code just before the_get_type()
function is implemented. - If your class implements one or more interfaces, you will declare them in
g_class_typedef()
.glib-utility.h
provides a helper macro,g_adopt()
, which removes some of the tedium from declaring the adoption of these interfaces. More information about interfaces is available in the GTK+ documentation. - Code pertaining to class structure initialization is placed in
g_class_init()
. This function is called once for every instance ofMamanBarClass
that is created; in other words, once forMamanBarClass
itself and then once for each descendant ofMamanBar
. You will usually use this space to initialize virtual method handlers and override those inherited from a parent class. Note thatg_class_init()
defines a parameter,Self
, which GObject fills in with a reference to the instance ofMamanBarClass
being initialized. More information on class construction is available in the GTK+ documentation. - Code pertaining to class instance initialization is placed in
g_instance_init()
. This function is called once for every instance ofMamanBar
and its descendants that is created. You will usually use this space to initialize instance variables. Note thatg_instance_init()
defines a single parameter,Self
, which GObject fills in with a reference to the instance ofMamanBar
being initialized. More information on instance initialization is available in the GTK+ documentation. - Code that has to do with class instance destruction is placed in
g_instance_dispose()
. This function is called whenever the reference count on an instance ofMamanBar
or its descendants reaches zero (through one or more calls tog_object_unref()
). It should only be called once on each instance ofMamanBar
but, due to errors in programming, it may be called more than once.g_instance_dispose()
defines a single local variable,Self
, which refers to the instance ofMamanBar
being destroyed. There is more information available in the GTK+ documentation. Note thatg_instance_dispose()
takes care of chaining up to the parent class. - Code that has to do with finalizing a class instance is placed in
g_instance_finalize()
.g_instance_finalize
defines a single local variable,Self
, which refers to the instance ofMamanBar
being finalized. There is more information available in the GTK+ documentation. Note thatg_instance_finalize()
takes care of chaining up to the parent class.
- You will likely never use