Hi all,
I've just been trying to solidify my understanding of OO techniques and how C can be used to implement things like "methods" from higher-level object-oriented languages that operate on the object on which they are called. Also things like macro expansion, variable arguments and structure function pointers.
I've written the code below and it seems to accomplish the desired aim but I'm curious to hear other people's thoughts on it and are you aware of other ways of achieving some level of OOP style code in plain C?
Sure, I could just use C++ and avoid a lot of this altogether by just defining a class but my goal has been to understand OO techniques in just plain C - for example, Glib/GObject and its inner workings.
Code:
#include <stdio.h>
#include <stdlib.h>
/**
* Macros that call the "method" of the object,
* passing in the "this" pointer so that the methods will work on
* the structure on which it is called.
*
* Note that method_call_va() is a slight variation of the normal
* method_call() but allows additional arguments to be passed into
* a method whose "signature" specifies parameters other than just the "this" pointer.
*/
#define method_call(inst, method) ((inst).method (&inst))
#define method_call_va(inst, method, ...) ((inst).method (&inst, __VA_ARGS__))
/**
* Define the object type.
* The function pointers correspond
* to functions we will write which then
* become the methods to objects of this type.
*/
struct Object_One
{
int X;
int Y;
char *label;
void (*get_data) (struct Object_One *item);
void (*set_data) (struct Object_One *item, int X, int Y);
};
typedef struct Object_One Object_One;
/**
* Methods that operate on instances of Object_One.
* Each function takes a pointer to an Object_One structure which
* equates to the "this" keyword in other languages and allows the current
* object (really, a structure) to be changed by the method.
*/
void object_one_get_data (Object_One *item);
void object_one_set_data (Object_One *item, int X, int Y);
int
main (int ac, char *av[ ])
{
Object_One object1; // First instance
// Set properties...
object1.X = 7;
object1.Y = 9;
object1.label = "Object 1";
// Assign "methods"...
object1.get_data = object_one_get_data;
object1.set_data = object_one_set_data;
// Call "methods"...
method_call (object1, get_data);
method_call_va (object1, set_data, 3, 6);
method_call (object1, get_data);
printf ("\n\n");
Object_One object2; // Second instance
// Set properties...
object2.X = 1;
object2.Y = 2;
object2.label = "Object 2";
// Assign "methods"...
object2.get_data = object_one_get_data;
object2.set_data = object_one_set_data;
// Call "methods"...
method_call (object2, get_data);
method_call_va (object2, set_data, 4, 7);
method_call (object2, get_data);
return EXIT_SUCCESS;
}
/**
* Object_One method implementations
*/
void
object_one_get_data (Object_One *item)
{
printf ("%s at %p -- X: %d, Y: %d\n", item->label,
item,
item->X, item->Y);
}
void
object_one_set_data (Object_One *item, int X, int Y)
{
item->X = X;
item->Y = Y;
}
The output from a run of this program is:
Code:
Object 1 at 0x7ffdfe42a2c0 -- X: 7, Y: 9
Object 1 at 0x7ffdfe42a2c0 -- X: 3, Y: 6
Object 2 at 0x7ffdfe42a2e0 -- X: 1, Y: 2
Object 2 at 0x7ffdfe42a2e0 -- X: 4, Y: 7
Which shows the two "instances" and how their X and Y values are changed by the method calls.