Next: Exception handling, Previous: Introduction, Up: Top [Contents][Index]
Class is the type of an Object instance. It specifies its data and function members and methods. We use the same terminology as in C++ here.
Before an Object could be used, it must be instantiated.
The Classes and Objects are located in the memory, as the following section describes.
The Class description table and the Virtual table are allocated statically in compilation time while the Object instances are allocated dynamically on the heap in run time. The Class allocation table is fully initialized at compilation time, while the Virtual Table must be initialized in run time before the Class is used. Due to this limitation it is not possible allocating Objects statically on the heap.
There is always a Virtual table, even if the class does not have a virtual function. In this case it is just a single pointer to the Class description table.
In ooc single inheritance is supported. The physical implementation of inheritance is embedding the parent class members into the beginning of the instantiated object.
Real multiple inheritance is not supported because of considering run time effectiveness on slower computers; plus trying to avoid complex inheritance problems that may occur in case of multiple inheritance, and a good solution for them would require more support from the compiler.
However since version 1.3 ooc supports the use of interfaces and mixins providing a kind of multiple inheritance.
In every class definition macros we use two parameters:
If a class is a base class (has no parent class), we shall mark it as it parent class was Base
. Therefore Base
is a reserved class name in ooc!
DeclareClass( String, Base ); /* String is a Base class */ DeclareClass( Utf8, String ); /* Utf8 is a class inherited from String */
Class definitions are basically nested struct definitions. That means that you can access data members via their names, as they were accessed as struct members. There is an important rule, that accessing the parent class’s data members requires a prefix with the parent class’s name before the data member name. This is because the standard ANSI C does not allow the use of unnamed struct, and I wanted to be ANSI compliant for better portability.
ClassMembers( String, Base ) char * cstr; int length; EndOfClassMembers; ClassMembers( Utf8, String ) int num_of_chars; EndOfClassMembers; /********************************** * Accessing data members */ String my_string; Utf8 my_utf8; int i; i = my_string->length; /* Accessing a class member */ i = my_utf8->num_of_chars; /* Accessing a class member */ i = my_utf8->String.length; /* Accessing class member inherited from the parent class */
A class member function is a normal C function, but there is a very important rule: the first function parameter of a member function is always a class instance object, and this first parameter can not be omitted.
void str_upper( String ); /* Declaring a member function */ void str_upper( String self ) /* Defining a member function */ { int i; assert( ooc_isInstanceOf( self, String ) ); for( i=0; i<self->length; i++ ) self->cstr[i] = cupper( self->cstr[i] ); } str_upper( my_string ); /* Calling a member function */
As a naming convention it is a good idea to start all class member function’s name with the name of the class, or with a meaningful abbreviation.
Virtual functions have the same requirement: their mandatory first parameter is an object instance pointer.
Virtual functions are implemented as static functions in the class implementation file, and the class’s virtual table holds pointers to these static functions.
Virtual functions are called via their function pointers in the vtable.
The vtable itself is basically a struct holding function pointers to the implemented static functions. The calling via these function pointers provides us the capability for compilation time type and parameter checking.
For virtual function calls we use the macros and inline functions.
For those compilers that the inline functions are not supported there is a function version for virtual function calls, but that is slower of course. This is the price for the better type safety.
/* Defining a virtual function */ Virtuals( String, Base ) int (* str_get_tokens)( String ); EndOfVirtuals; /* Calling a virtual function; from the user point of view */ int len; String my_string; len = StringVirtual( my_string )->str_get_tokens( my_string ); /* Implementing the virtual function in the class implementation file */ static int virtual_str_get_tokens( String self ) { /* doing some important here with self */ return result; } /* Initializing the virtual table in the class initialization handler */ static void String_init( ) { StringVtableInstance.str_get_tokens = virtual_str_get_token; }
Overriding the parent class’s virtual functions is very easy in the class implementation file. It can be done in the class initialization code.
/* Defining the virtual table */ Virtuals( Utf8, String ) /* In this case there are no new Virtual functions, only the parent's ones */ EndOfVirtuals; /* Implementing the virtual function in the class implementation file */ static int utf8_get_tokens( String self ) { /* doing some important here with self */ return result; } /* Overriding the parent's virtual function pointer in the virtual table in the class initialization handler */ static void Utf8_init( Class class ) { Utf8VtableInstance.String.str_get_tokens = utf8_get_token; } /* In the class's user code you can call the virtual in the same way: */ len = Utf8Virtual( my_utf8 )->String.str_get_tokens( (String) my_utf8 );
If you would like to call the parent class’s virtual function (this may be necessary in the implementation code when you would like to chain the new class’s virtual function to the parent’s one, you can use other macro accessing the original (non-overridden) function:
static int utf8_get_tokens( String self ) { /* doing some important stuff here with self, then chaining to the original parent's function: */ return Utf8ParentVirtual( self )->str_get_tokens( self ); }
The Class description table is completely hidden from the user of the class. It is a static struct in the heap, created and initialized at compilation time. The identifier of the class is the address of this class description table, so you must refer to the class with the "address of" operator and the class name.
You can allocate the class description table, the virtual table and some other helpers with a single macro:
AllocateClass( String, Base ); AllocateClass( Utf8, String );
Next: Exception handling, Previous: Introduction, Up: Top [Contents][Index]