© 2013-2019 by Zack Smith. All rights reserved.
Object-oriented programming was invented circa 1967 and is attributed to Alan Kay. OOP languages became dominant in the 1980's and 1990's with the simultaneous inventions of C++ and Objective C around 1980.
Object-Oriented C (OOC)
Due to the problems arising from certain object-oriented languages, it may be preferable to use a non-OOP language such as C but to augment it to support object-oriented principles. This is what I have done.
What does Object-Oriented C look like?
In my variation on this concept, code looks like this:
How can C support OOP?
- Encapsulation: by putting member variables inside an object struct and and method pointers inside a class struct.
- Polymorphism: by overwriting method pointers in the class struct.
- Inheritance: by including inherited method pointers into each class struct.
What are some real examples of object-oriented C?
My own benchmark bandwidth is now implmented in Object-Oriented C and I've written several classes for it e.g. string, array, image manipulation and graphing. My approach is evolving but is already fairly well refined.
GTK+, which uses glib, uses a type of object-oriented C.
Techniques for object-oriented C programming
Object and class structs
An object struct should be as small as possible,
containing only instance variables and a pointer to a class struct i.e.
I also use a retain count for memory management.
Here is an example of a class struct and an object struct.
The object itself is only 8+4+4+4=20 bytes, so you can fit 3 Shape objects in one typical 64-byte cache line. Presumably the ShapeClass is hanging around in the L1 cache as well and won't be too quicky displaced.
Methods pointers in the class struct.
This macro provides a simple and readable syntax:
There are a few different ways to implement the dollar sign macro.
Here is one that performs multiple safety checks:
What this does:
- Verify that the object pointer is non-NULL.
- Verify that the object pointer has an is_a pointer.
- Verify that the class struct has the method in question.
- If all above are true, perform the method call, else provide a 0 result.
- It supports any number of arguments.
In well-tested production code, it could be simplified to just the method call:
Call a non-overrideable method i.e. the non-polymorphic case.
Let's say a method will never be inherited and overridden in a derived class.
Calling an overrideable (i.e. polymorphic) super-class method.
It is necessary to put all methods, both parent class's and derived class's into the derived class's class struct, like so:
Any inherited super-class methods pointers can copied into the derived class's struct when each instance is initialized by simply calling the superclass' constructor.
Object struct layout
You should lay out your object structs such that instance variables of derived classes come after their parent classes' instance variables.
- The is_a pointer.
- Instance variables of parent class(es).
- Instance variables of your class.
- (Instance variables of further subclass here.)
To facilitate this, each class header file should provide macros that declare instance variables and methods, for example:
For your derived class, layouts of class struct and object struct would look like this:
Does this approach really have advantages over C++ or another OOPL? Yes.
Although it is a fully manual approach akin to driving a stick-shift car, because all object-oriented infrastructure is manually specified, there is no question about what is going on under the hood. The hood is open, or at least transparent. That level of certainty is reassuring.
When OOP functionality is implemented by someone else, you have to trust it was done well. With Object-Oriented C, you can verify this for yourself.
- OOC allows very fast code, as fast as C++.
- OOC allows small objects.
- My variant of OOC has a pleasing syntax:
$(object, method, parameters).
Many self-checks and novel protections can be put in place that, if you were using C++ or Objective C, would be in a layer of code that you wouldn't be able to examine or improve.
4. When you have no OOPL
For systems where no C++ compiler exists, this approach also presents a viable means for object-oriented programming.
Object-oriented programming in C can be more difficult than programming in an OO language like C++ or Objective-C.
You have to manually specify everything including manually building your class structs.
If there is ever a memory corruption issue debugging that can be a nightmare.