Click on our Sponsors to help Support SunWorld

Object-oriented
programming using C

All it takes is discipline, macros, structures, and pointers to functions to implement the concepts of OOP in ANSI C.

By Dave St.Clair

SunWorld
October  1995
[Next story]
[Table of Contents]
[Search]
Subscribe to SunWorld, it's free!

Abstract
Object-oriented programming is a language-independent technique. While there are many languages that make OOP easier than ANSI C, that doesn't mean a project written entirely in C cannot enjoy at least some OO benefits. This article discusses how programmers can start learning OO programming techniques using plain ANSI C. (1,800 words)


Mail this
article to
a friend

After years of relative obscurity, object orientation (OO) is entering the mainstream for software developers. What used to be locked up in academia and research labs is now being unleashed on unsuspecting masses. If you're observant, you can notice management cringing at the thought of complete retraining of a development team already proficient in structured programming.

Training in object-oriented programming (OOP) is important, but what good is it if you don't have any expertise in a language that supports OO? That means you'll need training in an object-oriented language. After all, don't you have to use C++, Objective C, Smalltalk or (for the adventurous) Eiffel? Nope. Remember, OOP is a language-independent technique. While there are many languages that make OOP easier than ANSI C, that doesn't mean a project written entirely in C cannot enjoy at least some OO benefits.

The basics
If you want a full explanation of what OO is, stop reading now and run down to the local bookstore and purchase any number of quality books on the subject -- Booch's "Object-Oriented Analysis and Design" comes to mind. After reading the following introduction, you'll find three of the key concepts that illustrate the advantages of the OOP approach are encapsulation, abstraction and polymorphism.

  1. Encapsulation.

    Encapsulation is the formal term that describes the bundling of methods -- functions within an object that determine its behavior -- and data together within an object so that access to the data is permitted only through the object's own methods. No other parts of an object-oriented program can operate directly on an object's data. Communication among a set of similar or different objects occurs exclusively through messages. A message is simply a request asking the object to behave in a certain way.

  2. Abstraction.

    OO encourages developers to think about applications in abstract terms. Applications are built thinking of sets of objects and pulling common behavior out into common objects or classes. Class libraries provide a repository for common reusable elements. Inheritance -- a method for automatically sharing methods and data among objects -- automatically maintains relationships among classes in a hierarchical format. This format is the basis for reusable class libraries.

  3. Polymorphism.

    Objects act in response to the various messages they receive. The same message can result in completely different actions when received by different objects. This is polymorphism. With polymorphism the implementation details of a given message is left up to the receiving object. For instance, assume there are two objects, a square and a circle. Sending each object the message "draw" will elicit different behavior from each object.


Advertisements


Some folks may get squeamish with pointers-to-functions,
but you will find they're worth the effort.


C building blocks
The question now arises, how can encapsulation, abstraction and polymorphism be accomplished in C? The answer is surprisingly simple and involves no concepts that can't be grabbed straight out of the C Bible (K&R's "C Programming Language"). There's nothing up my sleeve but structures, macros, and pointers-to-functions. Perhaps some folks get squeamish with pointers-to-functions, but you will find they're well worth the effort.

Objects in C
A structure is the only higher-level data object in C, so it must serve as the basic building block of an object. Two basic graphic objects could be defined:

typedef struct Circle {
	float radius;		/* radius of circle */
	Point location;		/* Center of circle */	
	Color color;		/* color of circle */
	} Circle;

typedef struct Square {
	float length;		/* size of one side */
	Point location;		/* lower left hand corner */
	Color color;		/* foreground/outline color */
	} Square;

This type of usage is nothing new nor is it particularly OO, but these objects can be extended to contain methods to help encapsulate the data. A minimum set of methods to add to both objects are:

The Square object would need a SetLength function while the Circle needs SetRadius.

By adding these methods to each class we have successfully encapsulated the data. Admittedly, there is nothing in C that prevents a developer from directly manipulating the attributes of each object, but we have provided methods that encourage proper usage.

To complete the encapsulation each class should add some message functions such as Draw() and Destroy().


After years of relative obscurity,
object orientation (OO) is entering the
mainstream for software developers.


Abstraction and inheritance
This is all well and good but what if we wanted to extend the graphics classes to include something like shading? We have to edit both classes and add the proper attribute and associated method. This is error prone and is one of the many problems OOP is supposed to solve. What we need to do is abstract common characteristics from the two objects into a common base class. In this simple example, the only attributes not common are length and radius so every other attribute may be grouped into the base class. With the help of a simple macro, a Shape base class is created:

#define SHAPE	ulong shade;		/* 3-D type shading */ \
		Point location;		/* lower left hand corner */ \
		Color color;		/* foreground/outline color */ \
		Status *(SetShade)(Shape *, ulong);	/* set 3d */ \
		Status *(SetColor)(Shape *, Color);	/* main color of object */ \
		Status *(SetLoc)(Shape *, Point);	/* base location */ \
		Status *(Draw)(Shape *);		/* size of the object */ \
		Status *Destroy(Shape * )		/* remove an object */ 

typedef struct Shape {
		SHAPE;
		} Shape;

typedef struct Circle {
		SHAPE;
		float radius;
		Status SetRadius(Circle *);
		} Circle;	

typedef struct Square {
		SHAPE;
		float length;
		Status SetLength(Square *);
		} Square;

Any additions of attributes and/or methods common to Circle and Square are now added to the macro SHAPE.

Nuts and bolts
A little divergence from straight OO is needed here to illustrate some of the mechanics involved in making a system like this work.

Each object requires an initialization function. This function is responsible for setting up both common and unique function pointers. Assuming there are files named circle.c, square.c and common.c:

common.c
	/* Define functions common to all classes */

	Status SetShade(Shape *shape, ulong shade)
	{
		shape->shade = shade;
		return(SUCCESS);
	}

	Status SetColor(Shape *shape, Color c)
	{
		shape->color = c;
		return(SUCCESS);
	}

	Status SetLoc(Shape *shape, Point p)
	{
		shape->location = p;
		return(SUCCESS);
	}
	
	Status Destroy(Shape *shape)
	{
	free(shape);
	}

circle.c
	/* define functions only for Circles */
	static Status Draw(Shape *shape)
	{
		Circle *circle = (Circle *)shape;

		/* some sort of drawing code here */
	}

	static SetRadius (Shape *shape, float r)
	{
		Circle *circle = (Circle *)shape;

		circle->radius = r;
		return(SUCCESS);
	}

	Circle * InitCircle(void)
	{
		Circle *circle = (Circle *)malloc(sizeof(Circle));
		
		circle->SetShade = SetShade;
		circle->SetColor = SetColor;
		circle->SetLoc = SetLoc;
		circle->Draw = Draw;
		circle->Destroy = Destroy;
		circle->SetRadius = SetRadius;

		/* any other initial values set here */
	}

square.c
	/* define functions only for Squares */
	static Status Draw(Shape *shape)
	{
		Square *sq = (Square *)shape;

		/* some sort of drawing code here */
	}

	static SetLength (Shape *shape, float r)
	{
		Square *sq= (Square *)shape;

		square->Length= r;
		return(SUCCESS);
	}

	Square * InitSquare(void)
	{
		Square *sq= (Square *)malloc(sizeof(Square));

		sq->SetColor = SetColor;
		sq->SetLoc = SetLoc;
		sq->Draw = Draw;
		sq->Destroy = Destroy;
		sq->SetLenth = SetLength;

		/* any other initial values set here */
	}

In circle.c and square.c, static functions are used to save name space.

Polymorphism
Polymorphism should be obvious at this point. Now a developer can tell a shape to draw and one of two things occur: Either a circle is drawn or a square. A fragment of a simple main function:

	int main(argc, argv)
	{
		int i, maxshapes;
		Shape *shape[MAXSHAPES];

		/* lots of setup code */

		/* read in a list of Shape's */
		maxshapes = ReadShapes(shape);

		/* Draw the shapes */
		for(i = 0; i < maxshapes; i++)
			shape[i].draw(shape[i]);

		/* Cleanup */
		for(i = 0; i < maxshapes; i++)
			shape[i].destroy(shape[i]);

	}
If new Shape's are added later, main need not be changed as it only deals with the highest level of abstraction. This also implies that as new attributes are added existing code should continue to work. This makes enhancements and bug fixes much simpler as only those modules directly changed need be retested.

A smart transition
All it takes is little bit of developer discipline, macros, structures, and pointers to functions to implement many of the basic concepts of OOP in ANSI C. Is this a suitable method for developing large, complicated systems? Definitely not. It relies too much on common development guidelines. But in many C-only groups, it may come in handy when deployed in moderation to show some quick benefits. Once benefit is shown, it should make it much easier to sell management on the transition to a true OO language.


Click on our Sponsors to help Support SunWorld


Resources


About the author
Dave St.Clair (dave.st.clair@sunworld.com) has been writing Unix software for a living for more than a dozen years and was bit by the OOP bug while learning C++ sometime in the 80's. He now splits his time between preaching the virtues of OOP and converting the masses to use Configuration Management tools effectively. St.Clair wrote a comparative review of CM tools in the July issue of SunWorld Online. Reach Dave at dave.st.clair@sunworld.com.

What did you think of this article?
-Very worth reading
-Worth reading
-Not worth reading
-Too long
-Just right
-Too short
-Too technical
-Just right
-Not technical enough
 
 
 
    

SunWorld
[Table of Contents]
Subscribe to SunWorld, it's free!
[Search]
Feedback
[Next story]
Sun's Site

[(c) Copyright  Web Publishing Inc., and IDG Communication company]

If you have technical problems with this magazine, contact webmaster@sunworld.com

URL: http://www.sunworld.com/swol-10-1995/swol-10-ooc.html
Last modified: