Reputation: 20139
Suppose I have the usual class Animal
abstract class and the class Dog : public Animal
class Cat : public Animal
that makes it a concrete class you can instantiate an object from. Suppose further that you have a function foo(Animal a)
, taking either cats or dogs as objects. C++ used to, in the early days, compile to C, and would build a vTable
maintaining the objects there.
But a student of mine asked me this question: before these concepts were usual talk among programmers, how did they actually do it in their day-to-day coding in C? What was (is?) the idiomatic way to program these concepts in C?
I've sifted through the linux kernel, and other OSS projects, but I haven't been able to find a clear pattern: sometimes it's unions (for different structs), sometimes it's function pointers, etc. but I would like a straight answer from knowledgeable people in industry who've done and have a lot of experience with C.
In one sentence: what is idiomatic C for inheritance and polymorphism?
Upvotes: 10
Views: 761
Reputation: 5164
I saw code that used #defines
to create abstract names in the source and include files. for example, the same routine might be re-used to deal with bytes, short
s, int
s, float
s, and double
s. The code was messy and hard to understand.
Upvotes: 0
Reputation: 11209
Before the advant of object oriented programming, applications were using procedural code where functionality was "encapsulated" in a hierarchy of functions with the ones at the top of the hierarchy working with abstractions and becoming more and more concrete and detailed as we go down the hierarchy. Some API provide ways to create structures that can only be manipulated via handles. Example: In C, file handles hide the details of dealing with files.
As for doing OO in C, I believe it was never a viable option without proper language support with the appropriate constructs. What you would get was not worth the pain involved in faking OO with pointers to functions.
Upvotes: -1
Reputation: 155286
Simple programs, such as those written for school assignments, implement polymorphism using a structure that consists of a union and optionally an enum as the type discriminators. Each "method" then contains a switch statement that calls into the function appropriate for the subtype. Obviously this doesn't scale to more systems that require being able to add subclasses without changing the definition of the base class.
Polymorphism itself is easily expressed with function pointers that receive an explicit self
argument. Open-ended inheritance can be achieved with the "inherited" structure embedding its superclass:
struct base {
// ... members here ...
};
struct inherited {
struct base base;
// ... inherited members here ...
};
Pointers to struct inherited
may be safely cast to struct base *
, a practice explicitly blessed by the C standard. These casts are typically hidden behind macros, which may even perform runtime type checking, where possible.
Implementing this is quite unwieldy, since there are no templates, no automatic invocation of destructors, no exceptions, and no STL. In other words, error handling and destructor invocation must be carefully handled by the programmer, and type variance must be handled either by callbacks at run-time (consider the difference between std::sort()
and qsort()
) or by hard to maintain preprocessor tricks.
Despite the difficulties, it is certainly possible to implement a meaningful subset of C++ functionality in C, and even a semblance of simplicity of C in the process. To study real-world examples of this approach being taken to production level, take a look at the implementation of the CPython interpreter, or the glib object system used by GTK+.
Upvotes: 8