OO Tips for C-II
OO Tips For The C Programmer Part II
Note: This article was published originally in the Cap Gemini America - Cincinnati Unit Newsletter.
Introduction
This is the second article in a series on Object Oriented programming techniques using 'C'. In the previous installment of this series of articles I explained how to use 'C' to define a class. The DISTANCE class was used as an example. In this installment encapsulation will be presented including how to define public and private members of a class. Encapsulation is the ability to hide the internal implementation details of an object from the outside world.
Basic Concepts
This section provides a brief explanation of some 'C' constructs that will be used later to support encapsulation.
Static Functions
A static function is a function that is only visible in the file that contains it. A static function is defined by adding the wordstatic at the beginning of the function definition. For example, if we have a file called test1.c that has the function:
static int square(int x) { return x*x; }
then the only functions that can use square() are functions defined in the file test1.c
Casting
Casting provides the ability to convert a value to a different type. To cast an expression, put the target data type in parentheses directly before the expression. For example:
float x; x= (float) 1;
assigns the int value 1 to the float variable x. In the previous installment we used casting in the constructor:
DISTANCE* ob=(DISTANCE*) calloc(1, sizeof(DISTANCE));
The calloc() function returns a void pointer, and we cast it to DISTANCE pointer in order to assign this value to ob without the compiler generating any warning messages.
In summary, if we have the cast: (MyType)MyValue we will be telling the compiler to treat MyValue as if it is of type MyType.
Encapsulation
We need to have a facility in which the methods of a class can only be accessed through the class. Additionally, the class may have members that should not be visible outside the class definition, i.e. private.
Methods Scope
For a class, only its definition should be visible by the class' users. The implementation of the class should be completely hidden. The solution is simple:
Put the class definition in a header file FileName.h
Put a prototype for the constructor in the header file.
Put the class implementation in a source file FileName.c
Define all the methods (except the constructor) in the source file to be static.
Now, whenever you want to use the class, include its header file.
For our DISTANCE class, put the class definition (i.e. struct) in distance.h, put the implementation of its methods in distance.c and make all the methods static (except ConstructDistance). For example:
static float DistanceKm(DISTANCE* this) { return this->DistanceMiles*1.609; }
Private Members
Although the class implementation is hidden from its users, all the class members are accessible by anyone who uses this class. We need to have a mechanism in which some of the class's members are inaccessible by the class users. In our DISTANCE class, suppose we want to be able to setand get the distance in both units of Miles or Kilometer.
To start, define the class definition as follows:
distance.h
typedef struct { void* private; void (*Destructor)(); void (*SetMiles)(); float (*GetMiles)(); void (*SetKm)(); float (*GetKm)(); } DISTANCE; DISTANCE* ConstructDistance();
Notice that there is no data member to store the distance value in the class definition. Additionally, there is a void pointer namedprivate.
Create the following type in distance.c
typedef struct { float DistanceMiles; } PrivateType;
Put in this PrivateType all the class members that you wish to make private.
In the constructor, assign the class member private to a memory allocated type, PrivateType:
ob->private= calloc(1, sizeof(PrivateType));
Update the destructor to free private:
free((*this)->private);
Methods of the class can access DistanceMiles as a member in a casted private, for example:
static void SetMiles(DISTANCE* this, float miles) { PrivateType* p= (PrivateType*)this->private; p->DistanceMiles= miles; }
Notice how private -which is a void pointer- is casted to a PrivateType pointer.
Remember to have 'DISTANCE* this' as the first argument of every method.
If anybody uses the DISTANCE class, they can not access the contents of PrivateType because it is defined in distance.c
Although the class has a public member private, this member is only a void pointer. The user of the class has no way of accessing the contents of this private without accessing the source file distance.c
The only discipline required from the class user is not to change the value of private after the class construction.
Conclusion
It is shown here that with utilizing some standard 'C' constructs we are capable of supporting encapsulation. This is achieved with a simple approach. Mainly, define all methods implementation to be static and have a class member that the user can not change (private). In the next installment of this series of articles, I will discuss inheritance and polymorphism then see if we could implement them using 'C'.
Acknowledgment
I would like to thank both of Carol Viera and Srinivasa Komatineni for their review and suggestions.
References
Stroutstrup, Bjarne. (1991) The C++ Programming Language, Second Edition. Addison-Wesley Publishing Company.
Darnell, Peter A. and Margolis, Philip E. (1988) Software Engineering in C. Springer-Verlag New York Inc.