Inheritance in C using structure composition
arpitbhayani.me
arpitbhayani.me
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.
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...
1: https://www.linuxtopia.org/online_books/gui_toolkit_guides/g...
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.
Then I realized I sometimes needed to downcast, so the base struct ended up with a type enum. And then I realized sometimes I wanted to call specialized functions, so I reinvented vtables. To this day I consider C++ an unwieldy over-complicated beast with a specification that is literally too complex for a verifiable implementation, but that brief exercise gave me a sufficient appreciation for the designers of C++ to respect their creation.
I did see it somewhat formalized in the late 1990s, though, with Apple's QuickDraw GX.
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?
So the typical advice would be to consider composition first, and to limit inheritance to genuine "is a" relationships as you say. But in some domains there are a lot of genuine "is a" relationships, so that does not in any way remove the use of inheritance.
Isn't the problem almost entirely multiple inheritance, then?
In terms of why to avoid inheritance, there are many reasons not tied to multiple inheritance, but the foremost one is to avoid exposing implementation details that may not make sense.
One thing to remember is that in OO public APIs "A is a B" really translates to "A's API is a superset of B's". Which is really not what we often think of as "is a" in daily speech. E.g. we might consider a Circle "is a" Ellipse to be a reasonable statement, because in day to day speech "is a" tends to denote a specialization that may be a superset and subset of different aspects of something at the same time, but if these objects are mutable, then allowing you to set all the parameters of an Ellipse for your Circle object will allow you to create a Circle object that is not actually a Circle.
So really, the point is to avoid inheritance unless you actually want to inherit and possibly expand on the full API of the class you're inheriting from. If I want a mutable Circle object, I shouldn't be inheriting from a mutable Ellipse - in fact in terms of API it may well make more sense for my Ellipse to inherit the API of a Circle.
You'll often find those kinds of inverted relationships in OO. Some types of OO can handle this - e.g. prototype based inheritance would allow you to simple remove methods that makes no sense in the subclass; alternatively you can trigger exceptions etc., but this violates what is very often a central contract of inheritance that users tends to rely on: That you can treat an object as if it is an object of its superclass in most contexts. It's better then to aim for subclassing to strictly superset the API.
[1] Yes, they have feelings too.
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.
let list_add = (n, prev, next) => {
prev[1] = n
n[1] = next
n[0] = prev
if (next) next[0] = n
}
foo = [null, null, 3]
bar = [null, null, 5]
list_add(bar, foo, null)
Looks a bit cooler with C structs names as offset.Inheritance would be building container hierarchy on top of that. Could be brittle in any language.
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...
The chapter I’m writing deals with types:
https://chris-sherlock.gitbook.io/inside-libreoffice/univers...