GObject 05: An Inheritable class.

In this article I will implement a class inheriting another.

An inherited class is also known as a "child class", a "derived class" or a "subclass".  The class it inherits is call the "parent class", the "base class" or the "superclass".  The subclass contains all the data its superclass has and all the methods, properties and signals its superclass has and hence plays whatever part its parent plays according to the Liskov Substitution Principle


Full code here.  A bit big.  Skip it.
/* A fundamental type.  Instantiatable and inheritable. */

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

/*** base class ***/

// Instance structure
typedef struct {
    GTypeInstance something_as_boilerplate;
    int a_base_instance_member;
} mybaseinstance_t;

// Class structure
typedef struct {
    GTypeClass something_as_boilerplate;
    void (*a_virtual_instance_method)(mybaseinstance_t *instance);
} mybaseclass_t;

// A virtual method (to be installed)
void my_base_method(mybaseinstance_t *instance) {
    printf("This is base.\n");
}

// Class init function
void class_init_func_of_base(mybaseclass_t *klass, gpointer data) {
    klass->a_virtual_instance_method = my_base_method;
}

// Instance init function
void instance_init_func_of_base(mybaseinstance_t *instance, gpointer data) {
    instance->a_base_instance_member = 42;
}

// Type registerer.
GType get_base_type() {
    static GType my_type_id=0;
    if(my_type_id==0) {
        static const GTypeInfo my_type_info = {
            sizeof(mybaseclass_t),  //class_size;

            NULL,   //base_init;
            NULL,   //base_finalize;

            /* classed types, instantiated types */
            (GClassInitFunc)class_init_func_of_base, //class_init;
            NULL,   //class_finalize;
            NULL,   //class_data;

            /* instantiated types */
            sizeof(mybaseinstance_t),//instance_size;
            0,      //n_preallocs;
            (GInstanceInitFunc)instance_init_func_of_base, //instance_init;

            /* value handling */
            NULL,   //value_table;
        };

        static const GTypeFundamentalInfo my_fundamental_info = {
            G_TYPE_FLAG_CLASSED | G_TYPE_FLAG_INSTANTIATABLE |
                G_TYPE_FLAG_DERIVABLE | G_TYPE_FLAG_DEEP_DERIVABLE
        };

        my_type_id = g_type_register_fundamental(
                g_type_fundamental_next(),
                "MyBaseClass",
                &my_type_info,
                &my_fundamental_info,
                0
                );
    }

    return my_type_id;
}

/*** derived class ***/

// Instance structure
typedef struct {
    mybaseinstance_t parent;
    int a_derived_instance_member;
} myderivedinstance_t;

// Class structure
typedef struct {
    mybaseclass_t parent_klass;
} myderivedclass_t;

GType get_derived_type(); // A prototype

// A virtual method (will override base virtual method)
void my_derived_method(mybaseinstance_t* instance) {
    myderivedinstance_t *derived = G_TYPE_CHECK_INSTANCE_CAST(
            instance, get_derived_type(), myderivedinstance_t);
    printf("This is derived.\n");
}

// Class init function
void class_init_func_of_derived(myderivedclass_t *klass, gpointer data) {
    mybaseclass_t *klass_as_base =
        G_TYPE_CHECK_CLASS_CAST(klass,get_base_type(),mybaseclass_t);
    klass_as_base->a_virtual_instance_method = my_derived_method;
}

// Instance init function
void instance_init_func_of_derived(myderivedinstance_t *instance, gpointer data) {
    instance->a_derived_instance_member = 65;
}

// Type registerer.
GType get_derived_type() {
    static GType my_type_id=0;
    if(my_type_id==0) {
        static const GTypeInfo my_type_info = {
            sizeof(myderivedclass_t),  //class_size;

            NULL,   //base_init;
            NULL,   //base_finalize;

            /* classed types, instantiated types */
            (GClassInitFunc)class_init_func_of_derived, //class_init;
            NULL,   //class_finalize;
            NULL,   //class_data;

            /* instantiated types */
            sizeof(myderivedinstance_t),//instance_size;
            0,      //n_preallocs;
            (GInstanceInitFunc)instance_init_func_of_derived, //instance_init;

            /* value handling */
            NULL,   //value_table;
        };

        my_type_id = g_type_register_static(
                get_base_type(),
                "MyDerivedClass",
                &my_type_info,
                0
                );
    }

    return my_type_id;
}

int main() {
    g_type_init();

    printf("Base: %d %s\n",get_base_type(), g_type_name(get_base_type()));
    printf("Derived: %d %s\n",get_derived_type(), g_type_name(get_derived_type()));

    /* Test the base class */
    mybaseinstance_t *binstance =
        (mybaseinstance_t*)g_type_create_instance(get_base_type());

    printf("base instance member: %d\n",binstance->a_base_instance_member);


    /* Test the derived class */
    myderivedinstance_t *dinstance =
        (myderivedinstance_t*)g_type_create_instance(get_derived_type());

    printf("derived instance member: %d\n",dinstance->a_derived_instance_member);

    mybaseinstance_t *derived_as_base =
        G_TYPE_CHECK_INSTANCE_CAST(dinstance, get_base_type(), mybaseinstance_t);
    printf("base member in derived: %d\n",
            derived_as_base->a_base_instance_member);


    /* Test polymorphism */
    mybaseinstance_t *instances[2] = { binstance, derived_as_base };
    int i;

    for(i=0;i<2;i++) {
        mybaseinstance_t *inst = instances[i];
        mybaseclass_t *klass =
            G_TYPE_INSTANCE_GET_CLASS(inst, get_base_type(), mybaseclass_t);
        klass->a_virtual_instance_method(inst);
    }

    return 0;
}



There are two classes and the second inherites the first.

In this program I define two classes separately.  The base class is define as before.
/*** base class ***/

// Instance structure
typedef struct {
    GTypeInstance something_as_boilerplate;
    int a_base_instance_member;
} mybaseinstance_t;

// Class structure
typedef struct {
    GTypeClass something_as_boilerplate;
    void (*a_virtual_instance_method)(mybaseinstance_t *instance);
} mybaseclass_t;

// A virtual method (to be installed)
void my_base_method(mybaseinstance_t *instance) {
    printf("This is base.\n");
}

// Class init function
void class_init_func_of_base(mybaseclass_t *klass, gpointer data) {
    klass->a_virtual_instance_method = my_base_method;
}

// Instance init function
void instance_init_func_of_base(mybaseinstance_t *instance, gpointer data) {
    instance->a_base_instance_member = 42;
}

// Type registerer.
GType get_base_type() {
    static GType my_type_id=0;
    if(my_type_id==0) {
        static const GTypeInfo my_type_info = {
            sizeof(mybaseclass_t),  //class_size;

            NULL,   //base_init;
            NULL,   //base_finalize;

            /* classed types, instantiated types */
            (GClassInitFunc)class_init_func_of_base, //class_init;
            NULL,   //class_finalize;
            NULL,   //class_data;

            /* instantiated types */
            sizeof(mybaseinstance_t),//instance_size;
            0,      //n_preallocs;
            (GInstanceInitFunc)instance_init_func_of_base, //instance_init;

            /* value handling */
            NULL,   //value_table;
        };

        static const GTypeFundamentalInfo my_fundamental_info = {
            G_TYPE_FLAG_CLASSED | G_TYPE_FLAG_INSTANTIATABLE |
                G_TYPE_FLAG_DERIVABLE | G_TYPE_FLAG_DEEP_DERIVABLE
        };

        my_type_id = g_type_register_fundamental(
                g_type_fundamental_next(),
                "MyBaseClass",
                &my_type_info,
                &my_fundamental_info,
                0
                );
    }

    return my_type_id;
}


Note the my_fundamental_info struct:
        static const GTypeFundamentalInfo my_fundamental_info = {
            G_TYPE_FLAG_CLASSED | G_TYPE_FLAG_INSTANTIATABLE |
                G_TYPE_FLAG_DERIVABLE | G_TYPE_FLAG_DEEP_DERIVABLE
        };

I added the G_TYPE_FLAG_DERIVABLE flag to allow this class to be inherited.

I also added G_TYPE_FLAG_DEEP_DERIVABLE so that the subclass can also be inherited.  Not all derivable types in GObject are deeply derivable.  "Interface" is one exception.  Unlike in Java and C#, an Interface cannot inherit another Interface in GObject, but it may have "prerequisites" which require other Interfaces to be also implemented.  This will be discussed in later articles.


Note the implementation of function get_base_type().  Here are some tricks:
  • On the first call, the type is registered and assigned to my_type_id.
  • my_type_id keeps its value between calls since it is static.
  • On subsequent calls to this function, the type id will be returned directly without registering the class again.
  • See Singleton Pattern for the above design pattern.


And note the class struct.
// Class structure
typedef struct {
    GTypeClass something_as_boilerplate;
    void (*a_virtual_instance_method)(mybaseinstance_t *instance);
} mybaseclass_t;

There is a member a_virtual_instance_method, which is a function pointer.  This is a virtual method of this class.  It is ready to be overridden by the subclass.


The derived class (subclass):
/*** derived class ***/

// Instance structure
typedef struct {
    mybaseinstance_t parent;
    int a_derived_instance_member;
} myderivedinstance_t;

// Class structure
typedef struct {
    mybaseclass_t parent_klass;
} myderivedclass_t;

GType get_derived_type(); // A prototype

// A virtual method (will override base virtual method)
void my_derived_method(mybaseinstance_t* instance) {
    myderivedinstance_t *derived = G_TYPE_CHECK_INSTANCE_CAST(
            instance, get_derived_type(), myderivedinstance_t);
    printf("This is derived.\n");
}

// Class init function
void class_init_func_of_derived(myderivedclass_t *klass, gpointer data) {
    mybaseclass_t *klass_as_base =
        G_TYPE_CHECK_CLASS_CAST(klass,get_base_type(),mybaseclass_t);
    klass_as_base->a_virtual_instance_method = my_derived_method;
}

// Instance init function
void instance_init_func_of_derived(myderivedinstance_t *instance, gpointer data) {
    instance->a_derived_instance_member = 65;
}

// Type registerer.
GType get_derived_type() {
    static GType my_type_id=0;
    if(my_type_id==0) {
        static const GTypeInfo my_type_info = {
            sizeof(myderivedclass_t),  //class_size;

            NULL,   //base_init;
            NULL,   //base_finalize;

            /* classed types, instantiated types */
            (GClassInitFunc)class_init_func_of_derived, //class_init;
            NULL,   //class_finalize;
            NULL,   //class_data;

            /* instantiated types */
            sizeof(myderivedinstance_t),//instance_size;
            0,      //n_preallocs;
            (GInstanceInitFunc)instance_init_func_of_derived, //instance_init;

            /* value handling */
            NULL,   //value_table;
        };

        my_type_id = g_type_register_static(
                get_base_type(),
                "MyDerivedClass",
                &my_type_info,
                0
                );
    }

    return my_type_id;
}


Note how this class is registered.  It is no longer a fundamental class because it is a subclass of another.
        my_type_id = g_type_register_static(
                get_base_type(),
                "MyDerivedClass",
                &my_type_info,
                0
                );
    }

So the GTypeFundamentalInfo is no longer needed.  I don't need to provide the numerical type-id, either.  I just tell GObject the base type, the class name and provide the GTypeInfo struct.

And see the structs
// Instance structure
typedef struct {
    mybaseinstance_t parent;
    int a_derived_instance_member;
} myderivedinstance_t;

// Class structure
typedef struct {
    mybaseclass_t parent_klass;
} myderivedclass_t;

The first members are always the corresponding struct of the base class.

Q: How does the derived class override the virtual method of the base class?
A: By modifying the function pointer in the class structure in THE DERIVED CLASS.
// Class init function
void class_init_func_of_derived(myderivedclass_t *klass, gpointer data) {
    mybaseclass_t *klass_as_base =
        G_TYPE_CHECK_CLASS_CAST(klass,get_base_type(),mybaseclass_t);
    klass_as_base->a_virtual_instance_method = my_derived_method;
}

In the class init function of the derived class, I cast the klass pointer to its parent type and then modify the a_virtual_instance_method in it.

In GObject, before a class is initialized, the class struct of its base class IS COPIED BYTE BY BYTE into the first portion of the class struct of this class.  Therefore the derived class automatically inherites all virtual methods from its parent.  Since the class structs of the parent and child are allocated separately, changing the class struct of the child does not modify its parent.


In the main function, I create an instance of the derived class.
    /* Test the derived class */
    myderivedinstance_t *dinstance =
        (myderivedinstance_t*)g_type_create_instance(get_derived_type());

    printf("derived instance member: %d\n",dinstance->a_derived_instance_member);

    mybaseinstance_t *derived_as_base =
        G_TYPE_CHECK_INSTANCE_CAST(dinstance, get_base_type(), mybaseinstance_t);
    printf("base member in derived: %d\n",
            derived_as_base->a_base_instance_member);

In order to access the members inherited from the base class, I cast the type using G_TYPE_CHECK_INSTANCE_CAST and it will do some runtime type checking.  Then I can access its parent's member from there.

The most exciting part is the polymorphism which allows the base class and the derived class act differently through the virtual method.
    /* Test polymorphism */
    mybaseinstance_t *instances[2] = { binstance, derived_as_base };
    int i;

    for(i=0;i<2;i++) {
        mybaseinstance_t *inst = instances[i];
        mybaseclass_t *klass =
            G_TYPE_INSTANCE_GET_CLASS(inst, get_base_type(), mybaseclass_t);
        klass->a_virtual_instance_method(inst);
    }

Since the type information is saved in the beginning of each instance, GObject can retrieve their respective class struct using the unified G_TYPE_INSTANCE_GET_CLASS macro.  Both of the the returned pointers have type mybaseclass_t, but they point to different class structs which contains different virtual method pointers.  That's why the "class structs" in GObject behave like "virtual method tables" in C++ and many other language implementations.

你可能感兴趣的:(prototype,Access)