2.7 Inheritance


2.7 Inheritance

After digesting the material in the previous sections, you should be quite familiar with the creation and use of classes and signals. This chapter's final topic is inheritance.

In principle, you already saw inheritance when you created the Media class, because it is a subclass of GObject . Here are the details of how to build a subclass:

  1. Define the instance structure with a pointer for the parent instance structure at the beginning.

  2. Define the class structure with a pointer to the parent class structure at the beginning.

  3. In the function that returns the new subclass, use the parent class type identifier as the first argument to g_type_register_static() .

  4. In the class initializer , install any new properties and signals. You may also install new default handlers for inherited signals.

The greater part of this section illustrates these steps, creating a CD class from Media . There is only one additional property, writable . A small demonstration of how to work with the new subclass follows the definitions.

To start, you need the usual instance and class structure definitions introduced in Section 2.2.1, as well as the macros from Section 2.2.2. Notice that writable is in the instance structure, but you need nothing else. Items such as set_property and get_property come from the parent structures.

 /******** CD (class derived from Media) **********/ typedef struct _CD {   Media media_instance;   gboolean writable; } CD; typedef struct _CDClass {   MediaClass media_class; } CDClass; #define TYPE_CD (cd_get_type()) #define CD(object) \    (G_TYPE_CHECK_INSTANCE_CAST((object), TYPE_CD, CD)) #define CD_CLASS(klass) \    (G_TYPE_CHECK_CLASS_CAST((klass), TYPE_CD, CDClass)) #define IS_CD(object) \    (G_TYPE_CHECK_INSTANCE_TYPE((object), TYPE_CD)) #define IS_CD_CLASS(klass) \    (G_TYPE_CHECK_CLASS_TYPE((klass), TYPE_CD)) #define CD_GET_CLASS(object) \    (G_TYPE_INSTANCE_GET_CLASS((object), TYPE_CD, CDClass)) static void cd_class_init(CDClass *class); 

Now you must provide a function to return the new class type identifier ( TYPE_CD ), as in Section 2.2.3. Note the parent class type identifier from the Media class (shown in boldface):

 GType cd_get_type(void) {   static GType cd_type = 0;   const GInterfaceInfo cleanable_info;   if (!cd_type)   {     static const GTypeInfo cd_info = {        sizeof(CDClass),        NULL,        NULL,        (GClassInitFunc)cd_class_init,        NULL,        NULL,        sizeof(CD),        16,        NULL     };     const GInterfaceInfo cleanable_info = {        cd_cleanable_init, NULL, NULL     };     /* Register type, use ID of parent class TYPE_MEDIA */     /* "CD" is too short, use "CompactDisc" instead */     cd_type = g_type_register_static(  TYPE_MEDIA  , "CompactDisc", &cd_info, 0);     /* add interface */     g_type_add_interface_static(cd_type, TYPE_CLEANABLE, &cleanable_info);   }   return cd_type; } 

Now you are almost ready to write the cd_class_init() class initializer, but first, you must provide some dependencies:

 /* CD constants and prototypes for properties */ enum {   PROP_0_CD,   PROP_WRITABLE }; static void cd_get_property(GObject *object,                             guint prop_id,                             GValue *value,                             GParamSpec *pspec); static void cd_set_property(GObject *object,                             guint prop_id,                             const GValue *value,                             GParamSpec *pspec); 
Note  

By now, the pedantic C programmer may be wondering why you would ever make prototypes for static functions. The prototypes just shown are not necessary if you define the functions before the cd_class_init() . In this book, it's primarily a matter of organization ” we didn't want to go into detail about properties before explaining the role of the class initializer.

For the sake of demonstration, this subclass replaces Media 's default signal handler for unpacked with this:

 /* a new default signal handler for unpacked */ static void unpacked_cd() {   g_print("Hi!\n"); } 

The class initializer is fairly straightforward; notice how replacing the default signal handler is a simple assignment after you get the parent class structure:

 /* CD class initializer */ static void cd_class_init(CDClass *class) {   GObjectClass *g_object_class;   MediaClass *media_class;   media_class = MEDIA_CLASS(class);   media_class->unpacked = unpacked_cd;   g_object_class = G_OBJECT_CLASS(class);   g_object_class->set_property = cd_set_property;   g_object_class->get_property = cd_get_property;   g_object_class_install_property(      g_object_class,      PROP_WRITABLE,      g_param_spec_boolean("writable", "Writable?",                           "Is the CD writable?", FALSE,                           G_PARAM_READWRITEG_PARAM_CONSTRUCT_ONLY)); } 

You set and retrieve writable as described in Section 2.5.1, but you may wonder how this works. After all, the preceding code overrides the set_property() and get_property() methods for the base class, and there is no mention of the parent's properties in the functions the follow.

The key to understanding this is that GObject initializes parent classes first. When a class installs its properties, GObject associates those properties with the class, and therefore, it can also look up the appropriate *property() functions based on that class.

 static void cd_set_property(GObject *object,                             guint prop_id,                             const GValue *value,                             GParamSpec *pspec) {   gboolean writable;   CD *cd = CD(object);   switch(prop_id)   {     case PROP_WRITABLE:        writable = g_value_get_boolean(value);        if (cd->writable != writable)        {           cd->writable = writable;        }        break;     default:        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);        break;   } } static void cd_get_property(GObject *object,                             guint prop_id,                             GValue *value,                             GParamSpec *pspec) {   CD *cd = CD(object);   switch(prop_id)   {     case PROP_WRITABLE:        g_value_set_boolean(value, cd->writable);        break;     default:        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);        break;   } } 

Now we're ready to use the new subclass. For the purposes of demonstration, assume that you also created another new subclass of Media called EightTrack for 8-track tapes. It adds a minutes property to Media , representing the total playing time of a tape.

You create objects and access properties as you would expect:

 Media *media; CD *cd; EightTrack *eighttrack; guint nr; gboolean is_unpacked;   << create a new media object >> /* create a new CD object */ cd = g_object_new(TYPE_CD,                   "inventory-id", 423,                   "writable", FALSE,                   NULL); /* verify data in the object */ g_object_get(cd,              "inventory-id", &nr,              "writable", &is_unpacked,              NULL); g_print("cd: writable = %s, inventory-id = %d\n",         is_unpacked? "true":"false", nr); /* create an EightTrack object */ eighttrack = g_object_new(TYPE_EIGHTTRACK, "minutes", 47, NULL); 

The following tests the signal handlers. Remember that the unpacked handler for CD is different now.

 /* EightTrack's unpacked handler; same as Media's */ g_signal_emit_by_name(eighttrack, "unpacked", NULL); /* CD's unpacked handler; expect "Hi!" instead */ g_signal_emit_by_name(cd, "unpacked", NULL); 

Finally, you can test various objects for membership in classes:

 /* is cd in Media? (subclass in parent; expect true) */ g_print("cd is %sMedia object\n", IS_MEDIA(cd)? "a " : "not a "); /* is eighttrack in Media? (subclass in parent; expect true) */ g_print("eighttrack is %sMedia object \n", IS_MEDIA(eighttrack)? "a " : "not a "); /* is media in CD? (parent in subclass; expect false) */ g_print("media is %sCD object\n", IS_CD(media)? "a " : "not a "); /* is cd in EightTrack? (expect false) */ g_print("cd is %sEightTrack object\n", IS_EIGHTTRACK(cd)? "an " : "not an "); 
Note  

You sometimes need access to the internals of the parent object in your object initialization and manipulation functions. If you want the Media parent object of cd , use the MEDIA() casting macro to get a Media object. Likewise, you can get at the parent class with MEDIA_CLASS() . The class initializer function cd_class_init() used this to override the unpacked signal handler.

2.7.1 Interfaces

In principle, an interface is nothing but a class with no objects and no regard for class hierarchy. Interfaces consist only of methods, and an object implements the interface when its class has all of these methods.

An interface's infrastructure includes an abstract interface type with a class structure, but no instance structure. Interfaces inherit all of their base characteristics from a base interface GTypeInterface (type identifier G_TYPE_INTERFACE ), much like a regular class inherits from GObject .

Defining an Interface

This section illustrates an interface called Cleanable that CD and EightTrack implement. Cleanable will include only one method: void clean(Cleanable *object) .

The structures are fairly trivial ” the instance structure is empty, and the class structure contains the parent interface and a pointer to the clean method:

 /* empty declaration for instance structure */ typedef struct _Cleanable Cleanable; /* Cleanable class structure */ typedef struct _CleanableClass {   GTypeInterface base_interface;   void (*clean) (Cleanable *object); } CleanableClass; 

Next, you must define a type identifier, casting, membership, and interface macros for Cleanable . Following the naming conventions in Section 2.2.2, TYPE_CLEANABLE() returns the result of cleanable_get_type() , IS_CLEANABLE() verifies that an object implements the interface, and CLEANABLE() casts an object to a cleanable type. CLEANABLE_GET_CLASS() returns the interface class, not the class of the object.

Another deviation from normal classes and objects is that you don't need CLEANABLE_CLASS or IS_CLEANABLE_CLASS , again, because there are no objects that belong strictly to the Cleanable interface.

 GType cleanable_get_type() G_GNUC_CONST; #define TYPE_CLEANABLE (cleanable_get_type()) #define CLEANABLE(object) \   (G_TYPE_CHECK_INSTANCE_CAST((object), TYPE_CLEANABLE, Cleanable)) #define IS_CLEANABLE(object) \   (G_TYPE_CHECK_INSTANCE_TYPE((object), TYPE_CLEANABLE)) #define CLEANABLE_GET_CLASS(object) \   (G_TYPE_INSTANCE_GET_INTERFACE((object), TYPE_CLEANABLE, CleanableClass)) 

The type initializer ( cleanable_get_type() ) is very similar to that of a class (see Section 2.4.1). However, there are a few differences:

  • You need only three fields in the GTypeInfo structure.

  • Base initializer and base finalizer functions are not NULL .

The code you're about to see also includes the base initializer and finalizer for the Cleanable interface. These do nothing more than manipulate a global variable that serves as a reference counter. When the counter goes from 0 to 1, or from 1 to 0, you may want to do something special with the interface. In the following examples, empty code blocks indicate where to place this code.

This process is somewhat clumsy, but it's necessary because interfaces are not derived from GObject and thus have no common class initializer.

 static guint cleanable_base_init_count = 0; static void cleanable_base_init(CleanableClass *cleanable) {   cleanable_base_init_count++;   if (cleanable_base_init_count == 1)   {      /* "constructor" code, for example, register signals */   } } static void cleanable_base_finalize(CleanableClass *cleanable) {   cleanable_base_init_count--;   if (cleanable_base_init_count == 0)   {      /* "destructor" code, for example, unregister signals */   } } GType cleanable_get_type(void) {   static GType cleanable_type = 0;   if (!cleanable_type)   {      static const GTypeInfo cleanable_info = {         sizeof(CleanableClass),         (GBaseInitFunc) cleanable_base_init,         (GBaseFinalizeFunc) cleanable_base_finalize      };      cleanable_type = g_type_register_static(G_TYPE_INTERFACE,                                              "Cleanable",                                              &cleanable_info,                                              0);   }   return cleanable_type; } 

Implementing and Installing an Interface

Every class that implements an interface must advertise the implementation. The relevant function call here is

 g_type_add_interface_static(  class_type_id  ,  interface_type_id  ,  info  ) 

Place this call in your type registration function (for example, cd_get_type() ). The arguments here are as follows:

  • class_type_id ( GType ): The implementing class type identifier.

  • interface_type_id ( GType ): The interface type identifier.

  • info ( const GInterfaceInfo * ): Contains these fields:

    • interface_init ( GInterfaceInitFunc ): GObject calls this function to initialize the interface when you cast an object from the implementing class to the interface.

    • interface_finalize ( GInterfaceFinalizeFunc ): GObject runs this function when the interface is no longer needed.

    • interface_data ( gpointer ): Optional data that you may pass to either of the preceding functions.

The code for installing the interface in cd_get_type() follows; eighttrack_get_type() is similar.

 static void cd_cleanable_init(gpointer interface, gpointer data); GType cd_get_type(void) {   static GType cd_type = 0;   if (!cd_type)   {      const GInterfaceInfo cleanable_info = {         cd_cleanable_init, NULL, NULL      };        << type initializing code >>      /* add interface */      g_type_add_interface_static(cd_type, TYPE_CLEANABLE, &cleanable_info);   }   return cd_type; } 

Here are the type definitions for GInterfaceInitFunc and GInterfaceFinalizeFunc from the GLib header files:

 typedef void(*GInterfaceInitFunc) (gpointer g_iface, gpointer iface_data); typedef void(*GInterfaceFinalizeFunc) (gpointer g_iface, gpointer iface_data); 

Normally, this class-specific interface initialization function does nothing other than verify that the interface is ready and then install the actual interface implementation function. For the CD class, this function is named cd_clean() .

When verifying the interface, recall from the previous section that the Cleanable class used a cleanable_base_init_count global variable to keep track of reference counts. If the interface is ready, that count is greater than zero:

 void cd_clean(Cleanable *cleanable); static void cd_cleanable_init(gpointer interface, gpointer data) {   CleanableClass *cleanable = interface;   g_assert(G_TYPE_FROM_INTERFACE(cleanable) == TYPE_CLEANABLE);   /* is the interface ready? */   g_assert(cleanable_base_init_count > 0);   cleanable->clean = cd_clean; } 

Here is the implementation of cd_clean() :

 void cd_clean(Cleanable *cleanable) {   IS_CD(CD(cleanable));   g_print("Cleaning CD.\n"); } 

The interface code for EightTrack is nearly identical.

One issue remains: how to bring the Cleanable implementations for CD and EightTrack into a single method called clean() . This method takes one Cleanable * object argument (an untyped pointer, of sorts) and runs the implementation that matches the class behind the object. The general procedure is as follows:

  1. If the argument doesn't implement the interface, abort.

  2. Retrieve the class-specific interface from the object.

  3. Add a reference to the object, so that GObject doesn't delete the object in the process of running the interface.

  4. Call the class-specific interface function.

  5. Remove the extra reference to the object.

Here is the code:

 void clean(Cleanable *object) {   CleanableClass *interface;   g_return_if_fail(IS_CLEANABLE(object));   interface = CLEANABLE_GET_CLASS(object);   g_object_ref(object);   interface->clean(object);   g_object_unref(object); } 

This short survey of the inner workings of interfaces is probably far more than you will ever need to know. In practice, you will use standard interfaces, not build your own. Therefore, this section concludes with a short demonstration of how to use the new interface on the cd and eighttrack objects created earlier in this chapter:

 clean(CLEANABLE(cd)); clean(CLEANABLE(eighttrack)); 



The Official GNOME 2 Developers Guide
The Official GNOME 2 Developers Guide
ISBN: 1593270305
EAN: 2147483647
Year: 2004
Pages: 108

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net