Tuesday, March 24, 2009

C can be used as object oriented language!

I am sick of C++ coders, who tend to yell their OOP terms to the world, and claim that OOP is the only way, and C++ is the best... Actually they're wrong when they say that C is a bad language, because it is not object oriented. They're wrong in two things.

1. C is far from bad language. Some of the most significant, most efficient, and most important things are still written in C. Linux, loads of embedded SW, (did you know that there is probably almost same amount of code in a modern car that there is in Windows XP? Scary thought.) and so on.

2. C can be seen as object oriented language, and sometimes it actually benefits to use it as such.

Actually, there is only a few things in C++, that you cannot do with C (not with same syntax of course, but with similar idea behind). The most important of those are probably destructors, templates, and overloading.

Did I hear lil C++ coder screaming about classes, virtual classes and inheritance? Allright. Maybe I'll explain a little further.

The most critical characters of object are:
1. It binds the data, and functionality (note, I avoided the "method" word... ehh.. Obviously I didnt :D )
2. It can be instantiated.

Yeah, how do you do that with C - you rat. I heard you, and don't worry, I'll explain.

I guess that even though you're so full of your fancy classes, you must've seen reserved word struct somewhere. At least when you did try to use it as a variable name last week. (Yes, the structs are in Cpp too). Then a bit more difficult thing, function pointer. If that's not a familiar concept to you (that exists in C++ too - although it is not as easy to use them there. Especially not for someone like me, who has not written C++ in... ... long time.) you can chek out my blog post here (in Finnish though).

So how to do a C representative for

class dog
EColour furColour;
int age;
unsigned int stomach_state;
bool check_condition();
int calculateStomachCapacity();
void die();
dog(EColour colour);
void feed(int food_amount);

dog::dog(EColour colour)

bool dog::check_condition()
if(calculateStomachCapacity() < 0 ...

Yeah, do that with C you say...

struct Sdog;
int dog_check_condition(Sdog *_this);

typedef int (*check_conditionF)(Sdog *_this);
typedef int (*calculateStomachCapacityF)(Sdog *_this);
typedef void (*dieF)(void);
typedef void (*dogF)(Sdog *_this, EColour colour);
typedef void (*feedF)(Sdog *_this);
typedef struct Sdog
EColour furColour;
int age;
unsigned int stomach_state;
check_conditionF check_condition;
calculateStomachCapacityF calculateStomachCapacity;
dieF die;
dogF dog;
feedF feed;

int dog_check_condition(Sdog *_this)
//starvation //overeating
if(_this->stomach_state == 0 || _this->calculateStomachCapacity(_this) <>stomach_state )
return 0;
return 1;
int dog_
void initDog(Sdog *_this, EColour colour)
and so on..

I hope you can see the analogy.

Okay okay. How about the second important thing in allmighty OOP:

The instantiation.

There can be several instances of dog ... blaa blaa blaa...

int main()
Sdog *lassie;

// or as a local variable:
Sdog lassie;

What's next? Data encapsulation? Allright.

This requires some "cheating", but what was the point of this blog is - it is doable.

Now we divide our Sdog into two different structs. Public Sdog, and private S_dog for example

typedef struct Sdog
dogF dog;
feedF feed;
char internalData[1];

typedef struct S_dog
//As abowe.

Now we place this struct Sdog and it's functions into a public header, but keep S_dog and private functions in private headers. The .c file (implementation of functions) then includes both headers, but only public one is given to dog users. When we initialize Sdog, we'll allocate space for S_dog too (in the placeholder)(by using realloc, or then we can make the struct allocation in dogInit() function, and change it to return the pointer to created object). And when we use internal functions, we'll just cast the placeholder (Eg. char internalData[1]) to S_dog * type.

Inheritance you say

Okay. This is rather simple. We can for example define structure Sdoberman, which has structure Sdog as it's first part, and append doberman specific data and functions at the end. Then when we wish to only use generic data, we'll handle the pointer to struct as Sdog, and when we want to use doberman features, we'll cast struct to Sdoberman.

Virtual classes someone whispers...

Yeah. Now what? If we have virtual class representative Sdog, and "classes" Sdoberman, Schihuahua and Smixed derived from virtual class. Now we can use aforementioned idea, and place Sdog struct as first item. We can also add a "dogType" variable in Sdog struct, which is initialized correctly when doberman, chihuahua or mixed is created. Then in virtual functions we can simply check the dogType variable, perform correct cast to _this pointer, and call correct frunction from Sdobermann, Schihuahua or Smixed.

Another approach (which also sounds more natural to C), is to have the "dogType" as first member in Sdog struct, use void * pointers, to the struct, dereference first sizeof(dogType) bytes and then perform the cast to correct dog struct.

I know. All C++ coders are terrified. But when a C coder looks at this, he/she notices the idea (I hope), and when we look the interface this creates, we'll really see the basic OOP structures.

A note about destructors and C

Furthermore, it is also possible to create a destructor type thing for your objects - when they're dynamically allocated with malloc() type call. It involves quite simple steps:

1. build your own malloc() wrapper (MyAlloc() for example), which allocates the space user requested + some extra for some internal structures. Then return pointer to this "user data area". Allow registration of callback function, which will be executed when memory is freed (the destructor), and store pointer to that in the extra space (which is invisible for user) you allocated. You can also create some additional debug things, like add some space after the user requested block, and write somekind of "end mark" to it. End mark which will be destroyed, if user overwrites the reserved memory... (This naturally requires storing the allocated blocks size in internal "header" too)

2. build your own free() wrapper, which executes the callback (destructor) and checks the endmark validity (if such was implemented), and frees the (internal + user) memory.

Final Words...

Okay. I think I have trolled enough now, and most of the C++ coders are so upset by now, that they have propably stopped reading ;) So I guess it is good time to tell what I really think. I think that C++ is great language, providing many things that are better than plain C. However C++ is not really usable in all environments, or at least C++'s best parts aren't. For example, in an embedded project where I was working I did one test using C++ and STL. Required stack size was tripled compared to C implementation. You may tell that it is because I used buggy compiler/buggy STL implementation. You can say this, you can say that. However fact is, that with C++, you'll easily get bloated code, larger executables, increased resource usage. You may be able to avoid these (partially), by limiting the C++ toolset you use - but then you cannot keep yapping how great the C++'s all cool features are, since you cannot really use all cool features. C++ is great for many purposes, but there is things where C is far better option.

I'll see if I can later show you some simple implementation of C++ style C - I have really sometimes used it, and it may be a good approach to some problems. Actually, I described one such situation in my Finnish blog here (In finnish blog => it is written in Finnish.)

No comments:

Post a Comment