One of the important concepts of object oriented programming is class inheritance. An object can inherit the properties and behaviour of another and extend them with its own ones. C isn't an OOP language so it can't have class inheritance, they say. Of course it can. Here is how to do it.
(if you haven't read my article about OOP methods in C you'll need to do so before reading this article).
I don't know how it is now but when I was student the essential example to teach inheritance was the 'Animal' class (and sometime 'Shape' when the teacher felt like using something a bit more useful). So, I'll perpetuate the tradition and use a class 'Animal', which is inherited by 'Bird' and 'Fish', and so on...
Do you remember the trick to improve readibility using anonymous unions ? Well, basically that's simply that. Lets first define the Animal class with just a name property, and two inheriting classes: Bird and Fish. The declaration of the Animal class will be reuse in each inheriting class, so I'm using the preprocessor to keep things clean and safe as explained in the link above.
Bird and Fish inherit name from Animal, which can be used just as if it was declared in the Bird and Fish class. Of course you can repeat the process as many times as you want. For example, lets declare a class Sparrow which inherits from Bird:
Sparrow also has access to the name of its grand-mother class just as if it was declared as its own property. Without anonymous struct, we would have to do something like theBird.parent.name and theSparrow.parent.parent.name, not fun ...
For those who haven't figured it out already, Bird and Fish can of course extend Animal with their own properties too. For example:
Nice, but pretty useless for now. Lets extend the Animal class with another property to make the example a bit more interesting.
With Animal getting more complex, repeating the initialisation of Animal's properties in BirdCreate() and FishCreate() becomes of course unsatisfying. We should really use AnimalCreate() instead, to avoid repeatition and because a real class instanciation function would probably do much more than those simple assignments. By setting the following rule: the parent of a class must always be the first property of that class, we guarantee that the address of one instance of the class is also the address of its parent, and grand-parent, and so on... It is then possible to initialise the parent as follow:
It (kind of) works and it's ugly. "kind of", because if the parent class as some const properties, the assignment isn't valid any more. To allow const properties we could use memcpy instead.
That works but it becomes a lot to type. Lets make it shorter thanks to the precompiler.
If Fish has its own properties, of course you can still initialise them, either before the parent:
or after, or even both, depending on your taste and needs:
The macro Inherits works for absolutely any class (as long as you respect a naming convention like 'ClassCreate' for the function creating an instance, but I know you're a clean programmer so you certainly already have such convention to rely on). Simply put it in a common header with other useful macros (like the one to use OOP methods in C maybe...) and never think of it again. Inheritance in C boils down to add the parent class as an anonymous property as the first property of another class, and call Inherits in the constructor function. Isn't it too good to be left unused ?
But wait, that's far from the end. Until now I've only spoken about properties. Basically it's 'structure inheritance', kind of. Lets add some methods and see how good real class inheritance becomes. I'll use the $() operator of LibCapy, cf the article I've told you to read, if you haven't, your bad.
First I create the print method in the class Animal:
Fish and Bird stay the same, and the main function becomes:
Bird and Fish inherits the method print, nothing particular to do here, and the internal of $() does what it takes to call Animal.print on a Bird, Fish or Animal instance as needed seemlessly. If necessary, inheriting classes could redefine the inherited methods by setting the function pointers to there own methods definition after Inherits:
Last example of the power of inheritance: it can also be used as a form of genericity. Imagine you need to implement a zoo, here simplified to a generic list of animals. Using a pointer to Animal as the type of the generic list (beware, not Animal itself or the data in the elements of the list may lack the extra properties defined in the inheriting classes), there is nothing to do but let the magic of inheritance works for us.
Even if the zoo is defined as a list of pointer to Animal (as proven by the necessary cast in add()), calling methods on its elements will works in accordance to the real type (Bird or Fish) of the data in each element thanks to the underlying casting in the $() and THAT_... macros.
In all that beauty, I see only one limitation: inheritance from several classes complexifies things in an unpleasant manner. You remember that everything works smoothly thanks to the inherited class being the first property of the inheriting class because it makes the address of both the same. With several inherited classes you can't take advantage of that any more. You would need to name the property for the instance of the inherited classes and cast with something like (ParentA*)&(that.parentA) and (ParentB*)&(that.parentB) wherever necessary. That becomes a mess and break all the simplicity of the method I've described in this article. So my advice is, stay away from multiple inheritance and enjoy my method with single inheritance only, there is already a lot of great stuff to do with that I think. If you really need multiple inheritance, you'll have to find another way, which I would be very please to hear about, so let me know !
Finally, the complete example code:
Edit on 2023/01/04:
More than a year later using the method described above repeatedly in LibCapy, I can say it's working great and very useful. However, I admit that "works on my machine" will never mean "works everywhere". Without a perfect knowledge of the standard, any unconventional use of the language always leaves the risk of a problem on some edge cases or tricky situation. Today I've learnt about one such situation thanks to a message on Reddit. Fortunately, I'm not using inheritance in a way that would produce that situation. So I'm safe for now. Still, I wanted to keep the reference here, for my future self and eventual readers. The problematic situation is as follow:
Quoting the author of the message:
"Try calling the above function from main as sar(p, a) (p and a are same as in your code). As p and c both point to the same object, the expression c->var1 = 0 should ensure p->var1 == 0. However, on gcc, compiling with -O2 (or above) optimization levels execute the above printf call due to a consequence of strict aliasing rule: struct parent and struct child are unrelated types, so their corresponding pointers (are assumed to) point to two distinct objects in memory; this gives the compiler liberty to assume that any change(s) made via the c pointer does not affect the data referred to via the p pointer (and vice-versa)."