
Inheritance in C using structure composition - arpitbbhayani
https://arpitbhayani.me/blogs/inheritance-c
======
ChrisSD
> Structures in C are not padded and they do not even hold any meta
> information, not even for the member names; hence during allocation, they
> are allocated the space just enough to hold the actual data.

I get that this is a simplification and that the point is there's no hidden
metadata at runtime but this is dangerously badly worded. Structures in C can
be padded, they just happen not to be in this particular case. All the fields
are 4 bytes and they're on a 32bit platform so these particular structures
will be packed.

That's not always going to be true however. For example, if you add something
that isn't a pointer or an int. Or compile on a platform with 64bit integers
and 32bit ints.

See also: [http://www.catb.org/esr/structure-
packing/](http://www.catb.org/esr/structure-packing/)

~~~
astrobe_
You mean 64 bits pointers and 32 bits integers, I guess.

~~~
ChrisSD
You're right, thanks. I'd edit but it's too late now.

------
twoodfin
This is exactly how C++ structures single-inheritance types without virtual
member functions.

Supporting virtual member functions (i.e. runtime polymorphism) only requires
adding a vtable pointer—which IIRC Linux also does for some of its own
structural subtyping, at least in its Virtual File System component.

Multiple inheritance requires some more bookkeeping by the compiler of
appropriate offsets, but structurally it doesn't change very much.

It's not surprising: C++ was famously originally implemented as a preprocess
transformation into C. _Inside the C++ Object Model_ [1] is a fascinating deep
dive on how C++ semantics map to C constructs.

[1] [https://www.oreilly.com/library/view/inside-
the-c/0201834545...](https://www.oreilly.com/library/view/inside-
the-c/0201834545/)

~~~
identity0
I don’t know if this is actually how C++ compilers do inheritance, but reading
about how GTK does object-oriented in C[1] taught me a lot about what (might
be) happening behind the scenes in C++.

1:
[https://www.linuxtopia.org/online_books/gui_toolkit_guides/g...](https://www.linuxtopia.org/online_books/gui_toolkit_guides/gtk+_gnome_application_development/cha-
objects.html)

------
ChrisMarshallNY
It's a bit wild, seeing this. I was doing it with C in the mid-1990s. In fact,
I designed an entire SDK around it, that is still used, to this day.

I called it my "faux object" pattern, and I used a lot of function pointers
and property pointers (like the OP mentioned). That allowed a "poor man's
polymorphism." I may have actually published it as a pattern, but I'm not sure
anyone ever read it.

The reason that I did it, was that C++ was still clambering out of the
bassinet, back then, and we needed a cross-platform way to translate stuff
that required an object model, across an opaque binary interface. We used C to
transfer the state and data, and built object frameworks on either side of the
coupling, with C++ or Object Pascal.

On the Mac platform, we also used Pascal types, which ensured a predictable
stack frame.

It was pretty klunky, but it worked.

~~~
icedchai
The Amiga OS used this pattern everywhere. I first learned C on an Amiga in
the late 80’s.

~~~
ChrisMarshallNY
I believe that. I know that I learned it somewhere, but the origin is lost in
the mists of antiquity.

I did see it somewhat formalized in the late 1990s, though, with Apple's
QuickDraw GX.

------
m4r35n357
Dumb but genuine question from a non-expert programmer:

Most of the "OO" world seems to have abandoned inheritance as an anti-pattern
in most cases (apart from genuine "is a" relationships).

I'm guessing the two case studies are in the list of exemptions from this
rule.

So, is this considered a generally good thing to do in c?

~~~
astrobe_
I believe this is because of Liskov's substitution principle (LSP), which is
the hidden gotcha that awaits around the corner beyond the "manager is-a
person" [1] example. The "square is-a rectangle... or maybe not" discussions
that pop up from time to time is an illustration of the problem. In practice,
when you produce a lot of classes, derive a lot classes (because of the open-
close principle), it is easy to blunder.

[1] Yes, they have feelings too.

~~~
vidarh
The problem tends to be that "is-a" is conveniently short but it confounds
several different types of inheritance.

Inheritance in most OO languages really means "api is a superset of, and
implementation is a specialization (and possibly superset) of" while in
natural language "is-a" implies a relationship where some facets might be
expanded, while others may be more restricted.

This is really partially a weakness in expressiveness of our languages,
partially a problem of our frequent insistence on mutability. Often the
problem goes away with immutability: If your "square" is immutable and
inheriting from a "rectangle" class that allows setting width and height to
different values isn't a problem, because the result would be a new object and
that result can be a rectangle.

But sometimes we also genuinely would be best served by inheriting
implementation and public API separately without having to create cumbersome
facades etc. - it's just that making an API of a subclass a subset of the API
of the superclass violates a lot expectations people tend to have about OO
systems, and so few systems outside of prototype based ones seem to allow it
without resorting to ugliness like overriding methods to make them throw
exceptions etc.

------
chris_wot
LibreOffice does this to power it’s typelib library, which is part of UNO.

I’m trying to figure out how this stuff works right now by building unit tests
so I can add to my work in progress book.

[https://github.com/chrissherlock/libreoffice-
experimental/bl...](https://github.com/chrissherlock/libreoffice-
experimental/blob/uno/cppuhelper/qa/type/test_typelibstruct.cxx)

The chapter I’m writing deals with types:

[https://chris-sherlock.gitbook.io/inside-
libreoffice/univers...](https://chris-sherlock.gitbook.io/inside-
libreoffice/universal-network-objects)

------
unixguy1337
Check
[https://www.cs.rit.edu/~ats/books/ooc.pdf](https://www.cs.rit.edu/~ats/books/ooc.pdf)
from 1993. It has been mentioned and discussed earlier on HN, like
[https://news.ycombinator.com/item?id=7011540](https://news.ycombinator.com/item?id=7011540)

------
sd314
C is not designed for OOP. You will write much nicer C code without such
patterns.

~~~
haberman
I agree. While it is occasionally can work alright (as with the examples in
the article), I recommend a very light touch with this stuff. If you get to
the point where you are implementing casts, RTTI, etc, stop and rethink. I say
this as someone who has gone down this path and later regretted it.

~~~
User23
In a professional context I couldn't agree more. However for a hobby project I
couldn't disagree more. Implementing safe down-casting (up is trivial),
dynamic dispatch, encapsulation, and other OO(ish) features is an absolutely
wonderful learning experience. Like most first attempts, it will probably be a
mess as you say, but there can be a joy in making a mess of things so long as
you're not inflicting it on the unsuspecting.

~~~
haberman
Yes I agree that for learning purposes it can be very illuminating. :)

