Just as with C++, there are many different ways to define object methods and extend them: the following list and sections draw on C++ vocabulary. (Readers are expected to know basic C++ concepts. Those who have not had to write C++ code recently can refer to e.g. http://www.cplusplus.com/doc/tutorial/ to refresh their memories.)
non-virtual public methods,
virtual public methods and
virtual private methods
These are the simplest, providing a simple method which acts on the object. Provide a function prototype in the header and an implementation of that prototype in the source file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
/* declaration in the header. */ void viewer_file_open (ViewerFile *self, GError **error); /* implementation in the source file */ void viewer_file_open (ViewerFile *self, GError **error) { g_return_if_fail (VIEWER_IS_FILE (self)); g_return_if_fail (error == NULL || *error == NULL); /* do stuff here. */ } |
This is the preferred way to create GObjects with overridable methods:
Define the common method and its virtual function in the class structure in the public header
Define the common method in the header file and implement it in the source file
Implement a base version of the virtual function in the source
file and initialize the virtual function pointer to this
implementation in the object’s class_init
function; or leave it as NULL
for a ‘pure
virtual’ method which must be overridden by derived classes
Re-implement the virtual function in each derived class which needs to override it
Note that virtual functions can only be defined if the class is
derivable, declared using
G_DECLARE_DERIVABLE_TYPE
so the class structure can be defined.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
/* declaration in viewer-file.h. */ #define VIEWER_TYPE_FILE viewer_file_get_type () G_DECLARE_DERIVABLE_TYPE (ViewerFile, viewer_file, VIEWER, FILE, GObject) struct _ViewerFileClass { GObjectClass parent_class; /* stuff */ void (*open) (ViewerFile *self, GError **error); /* Padding to allow adding up to 12 new virtual functions without * breaking ABI. */ gpointer padding[12]; }; void viewer_file_open (ViewerFile *self, GError **error); /* implementation in viewer-file.c */ void viewer_file_open (ViewerFile *self, GError **error) { ViewerFileClass *klass; g_return_if_fail (VIEWER_IS_FILE (self)); g_return_if_fail (error == NULL || *error == NULL); klass = VIEWER_FILE_GET_CLASS (self); g_return_if_fail (klass->open != NULL); klass->open (self, error); } |
The code above simply redirects the open
call
to the relevant virtual function.
It is possible to provide a default
implementation for this class method in the object's
class_init
function: initialize the
klass->open
field to a pointer to the
actual implementation.
By default, class methods that are not inherited are initialized to
NULL
, and thus are to be considered "pure virtual".
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
static void viewer_file_real_close (ViewerFile *self, GError **error) { /* Default implementation for the virtual method. */ } static void viewer_file_class_init (ViewerFileClass *klass) { /* this is not necessary, except for demonstration purposes. * * pure virtual method: mandates implementation in children. */ klass->open = NULL; /* merely virtual method. */ klass->close = viewer_file_real_close; } void viewer_file_open (ViewerFile *self, GError **error) { ViewerFileClass *klass; g_return_if_fail (VIEWER_IS_FILE (self)); g_return_if_fail (error == NULL || *error == NULL); klass = VIEWER_FILE_GET_CLASS (self); /* if the method is purely virtual, then it is a good idea to * check that it has been overridden before calling it, and, * depending on the intent of the class, either ignore it silently * or warn the user. */ g_return_if_fail (klass->open != NULL); klass->open (self, error); } void viewer_file_close (ViewerFile *self, GError **error) { ViewerFileClass *klass; g_return_if_fail (VIEWER_IS_FILE (self)); g_return_if_fail (error == NULL || *error == NULL); klass = VIEWER_FILE_GET_CLASS (self); if (klass->close != NULL) klass->close (self, error); } |
These are very similar to virtual public methods. They just don't have a public function to call directly. The header file contains only a declaration of the virtual function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
/* declaration in viewer-file.h. */ struct _ViewerFileClass { GObjectClass parent; /* Public virtual method as before. */ void (*open) (ViewerFile *self, GError **error); /* Private helper function to work out whether the file can be loaded via * memory mapped I/O, or whether it has to be read as a stream. */ gboolean (*can_memory_map) (ViewerFile *self); /* Padding to allow adding up to 12 new virtual functions without * breaking ABI. */ gpointer padding[12]; }; void viewer_file_open (ViewerFile *self, GError **error); |
These virtual functions are often used to delegate part of the job to child classes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
/* this accessor function is static: it is not exported outside of this file. */ static gboolean viewer_file_can_memory_map (ViewerFile *self) { return VIEWER_FILE_GET_CLASS (self)->can_memory_map (self); } void viewer_file_open (ViewerFile *self, GError **error) { g_return_if_fail (VIEWER_IS_FILE (self)); g_return_if_fail (error == NULL || *error == NULL); /* * Try to load the file using memory mapped I/O, if the implementation of the * class determines that is possible using its private virtual method. */ if (viewer_file_can_memory_map (self)) { /* Load the file using memory mapped I/O. */ } else { /* Fall back to trying to load the file using streaming I/O… */ } } |
Again, it is possible to provide a default implementation for this private virtual function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
static gboolean viewer_file_real_can_memory_map (ViewerFile *self) { /* As an example, always return false. Or, potentially return true if the * file is local. */ return FALSE; } static void viewer_file_class_init (ViewerFileClass *klass) { /* non-pure virtual method; does not have to be implemented in children. */ klass->can_memory_map = viewer_file_real_can_memory_map; } |
Derived classes can then override the method with code such as:
1 2 3 4 5 6 7 8 |
static void viewer_audio_file_class_init (ViewerAudioFileClass *klass) { ViewerFileClass *file_class = VIEWER_FILE_CLASS (klass); /* implement pure virtual function. */ file_class->can_memory_map = viewer_audio_file_can_memory_map; } |