HowTo |
Use Object Oriented Coding Techniques in C | Prof. Bartenstein |
The C language is an imperative language which pre-dates the concepts of object orientation. When it became clear that object oriented techniques brought a new level of rigor and precision to programming, the C++ language was invented to fully implement an object oriented language derived from C. However, I find that C++ is a very difficult language to code in. It's just too easy in C++ to write code that intuitively seems correct, but which has hidden bugs.
One alternative is to use several of the object oriented techniques without using all of the complexity introduced by C++; instead, sticking with the simple C language, plus an object oriented discipline that enables many of the advantages of a full object oriented implementation without the complexity.
In object oriented thinking, the things our program works with can be thought of as on object in a specfic class. A class consists of a definition of fields, the data that describes an object; and methods, the actions that can be performed on that object.
A class typically has a level of encapsulation. Most often, users of the class can invoke methods in that class, but do not have direct access to the fields in the class. All methods and field definitions are collected (encapsulated) in a single file used to define the class, managed by the owner of that class. Furthermore, classes become a specific data type, and we can declare reference variables using that data type to point to instances of objects in that class.
New objects in a class are often created by a creator method, and deleted by a delete method. If the coder of a class wants to allow users to read or write specific fields, she will often provide getter and setter methods for those fields as well.
When coding in C, we can use techniques to mimic these concepts, as follows:
In a C object oriented environment, the class_name.h file will contain declarations for all methods in the class, and a typedef to create a data type for that class. The class_name.c file will contain a structure definition that defines all of the fields for the class, and function definitions for all of the methods in the class.
class_name_struct. The fields or members of this structure can be any valid C declaration, including built-in data types like int or float; arrays and/or pointers like char*; and "reference" variables which are pointers to other objects.In a true object oriented language, you can use the same method names for different classes. The object oriented infrastructure chooses which method to invoke based on the reference variable that invokes the method. When using object oriented techniques in a simple language like C, no such infrastructure exists. Therefore, we must make each method name unique across all classes. The easiest way to accomplish method name uniqueness is to include the class name as part of the method name. For example instead of coding a move method in the ball class, we will code a ball_move class in the ball class. Then, we know it has a different method name than (for example) the bat_move method in the bat class.
The capability to create a reference to a structure that has not yet been defined is called forward declaration in C. A forward declaration allows us to create a pointer to a structure without knowing the details about that structure, along with the promise that by the time we use that pointer to reference specific fields in the structure, the definition of the structure will be known.
The class data structure which defines the fields is coded in the class_name.c file. The result is that only the code in the class_name.c file can access the fields in that structure.
Using a forward declaration in a typedef of a pointer to an undefined structure is called an Abstract Data Type, and it provides encapsulation without a complex object oriented infrastructure.
typedef struct ball_struct * ball;
This defines the ball type as a pointer to an instance of the structure ball_struct.class_name_create. The parameters to this function will specify the values needed to create the object. This function will be declared in class_name.h and defined in class_name.c. It will malloc space for an instance of the class data structure: class_name_struct, initialize the fields in that structure, and return a pointer to the structure which can be used as a reference to that object.
You can code extra creators with different inputs, but they must have unique function names (like class_name_create_fromSphere) that has different parameters, but performs the same functions.
class_name_delete. This function should have a single parameter, a reference to the object to delete. The function should return void. This function will be declared in class_name.h and defined in class_name.c. It should invoke free for the parameter pointer to the class structure after taking care of any allocations made for the object (if any.)class_name_get_field_name that take a single parameter - a reference to an object, and return the value of the field. These functions are declared in class_name.h, and defined in class_name.c.class_name_set_field_name that take a two parameters - a reference to the object, and the new value for the field. Setter functions by convention return void. These functions are declared in class_name.h, and defined in class_name.c.The techniques described on this web page allow you to do some object oriented style programming using simple C code, but obviously you can't do everything you could do with a full object oriented language like C++. The following is an incomplete list of the things you can't do with these techniques:
instance.method(arglist) syntax, or, if it's a reference, using reference->method(arglist). Neither syntax is supported in simple C, so the methods need to be invoked like a function in C, namely class_method(reference,arglist). One side-effect of this difference is that there is no capability to distinguish between methods with the same name in C. That means each method must have a unique name. Hence the convention to prefix the method name with the class name, as in ball_move for a move method in the ball class. This make polymorphism virtually impossible with just simple C code.Object oriented design using the techniques is very similar to object oriented design in a true OO language. You need to decide what classes you will need, and for each class, what are the fields and methods required for that class. Of course, without inheritance, you won't need to worry about parent and child classes, but you can still get a lot done without inheritance.
See the How To Write a program using Multiple Files web page for details on coding a generic header file. We need to include at least two things in the object oriented C class header file: a typedef to define the type associated with the class, and method declarations. The typedef simply defines the class as a pointer to the class structure. For the ball class, this would be:
typedef struct ball_struct * ball;
The method declarations are relatively straightforward function declarations. For the ball class, they might be something like:
ball ball_create(float radius,color ballColor);
float ball_get_radius(ball b);
color ball_get_color(ball b);
void ball_set_radius(ball b,float newRadius);
void ball_set_color(ball b,color newColor);
void ball_delete(ball b);
So, the entire ball.h header file might look something like:
#ifndef BALL_H
#define BALL_H
#include "color.h" // My "color" object to keep track of colors
typedef struct ball_struct * ball;
ball ball_create(float radius,color ballColor);
float ball_get_radius(ball b);
color ball_get_color(ball b);
void ball_set_radius(ball b,float newRadius);
void ball_set_color(ball b,color newColor);
void ball_delete(ball b);
#endif
See the How To Write a program using Multiple Files web page for details on coding a generic code file. The class_name.c file should start with any #includes required, including including the class header file, and header files for any other parts of your project or system functions used in the implementation of the methods. For example, for the ball class, you might have:
#include "ball.h"
#include <stdlib.h> // for malloc and free
#include <stdio.h> // for debug messages
Next, we need to define the class structure which contains all the fields. For the ball class, this might look like:
struct ball_struct {
float radius;
color color;
}
Next is the creator method. Here's an example:
ball ball_create(float radius,color ballColor) {
ball b = malloc(sizeof(struct ball_struct));
b->radius=radius;
b->color=ballColor;
return b;
}
Getter and setter methods are pretty much one-liners, e.g.:
float ball_get_radius(ball b) { return b->radius; }
void ball_set_radius(ball b,float newRadius) { b->radius=newRadius; }
Obviously, you will be coding more complicated methods that do much more than this simple code, but you get the idea. Finally, the delete method code is very straightforward for the simple ball class:
void ball_delete(ball b) { free(b); }
Note that if the object itself allocated any memory, the delete code would free that memory before freeing up the structure instance itself.
There's not much about these techniques, but check out the Wikipedia article on Forward Declaration and Abstract Data Type. Also, here's a slightly different take from Software Engineer Dmitry Frank, using slightly different techniques: Object-oriented tehcniques in C. And a University of Washington web page titled Ojbect Oriented C Programming.