OOP mimicry in C !!!
Date: 2024/07/30 (Tue)
I am recently programming my own SDL game engine in the PRETENTIOUS language, C, but what should we do if there's no object oriented syntax in it ? Then it must be cool to make it ourselves. Or we can think in this way: let's see Python, C++, Perl or what ever OO language. They are all written in C. So don't listen to people complaining about C not having OO. It can absolutely be done.
Private and Member Functions
- private variables: underscore naming convention
int _value;
- private functions:
static
functions with one class per file, yeah, just like Java - memeber functions: pass a self pointer
this
in functions like the fanciest language, Pythontypedef example { int _width; int _height; } example_t; // private function static int calculate_area(example_t* this) { return this->_width * this->_height; } // public function / encapsulation int example_get_width(example_t* this) { return this->_width; }
- the usage of a member function:
example_t ex; int width = example_get_width(&ex);
- or even better, more concise with a function pointer variable and designated initializer.
typedef example { int _width; int _height; // function pointer int (*get_width)(struct example*); } example_t; int example_get_width(example_t* this) { return this->_width; } // usage example_t ex = { ._width = w, ._height = h, .get_width = example_get_width }; int width = ex.get_width(&ex);
- we can further make the function static to have shorter name (without the class name prefix) and get rid of the naming collision with stuffs in other header files. And then assign it in a constructor, where we will later benefit from it while doing inheritance.
// make it private static int get_width(example_t* this) { return this->_width; } // then assign in constructor example_constructor(example_t* this) { this->get_width = get_width; } // usage example_t ex; example_constructor(&ex); int width = ex.get_width(&ex);
Inheritance
- Just include base class in derived class, and note that it must be the first member. Since the memory alignment mechanism of struct in C, by this way we can directly cast the
derived_class_t
tobase_class_t
without any additional manipulation.typedef struct derived_class { base_class_t base; // additional variables int _var1; int _var2; } derived_class_t;
Polymorphism
- overriding
- cast with function pointer
- note that: without definition, we can also have a pure virtual function / abstract.
typedef struct base_class { int _var1, _var2; // virtual function void (*func1)(int, int); } base_class_t; typedef struct derived_class { // have to be the first one base_class_t base; // additional variables int _var3, _var4; // functions void (*func1)(int, int); } derived_class_t;
- we have to have a constructor function to cast the
base_class_t::func1
toderived_class_t::func1
.
void derived_class_constructor(derived_class_t* this) { // overriding this->func1 = this->base.func1; } // usage: derived_class_t derived; derived->func1((base_class_t*)&derived_class);
- what if we don't like manipulating the type casting of base class when using the derived one? just wrap it more with a static function
static void derived_class_func1(derived_class_t* this) { this->base.func1(&this->base); } void derived_class_constructor(derived_class_t* this) { // overriding this->func1 = this->derived_class_func1; } // usage: derived_class_t derived; derived->func1(&derived_class);
- private virtual function
- too long ? try using MACRO to simplify into C++ syntax
#include "logging.h"
#define new(T, ...) ({ \
T##_t* this = (T##_t*) malloc(sizeof(T##_t)); \
logging(this, #T" created.", "creating "#T" failed."); \
T##_constructor(this, ##__VA_ARGS__); \
this; })
#define delete(T) ({ free(T); T = NULL; })
- overloading with
_Generic
? - segmentation fault when library linking and solved with VTable
Conclusion
Boom !! Want to learn OOP ? Then you should definitely try C !!