From kragen@dnaco.net Thu Jul 30 08:12:04 1998 Date: Thu, 30 Jul 1998 08:12:04 -0400 (EDT) From: Kragen To: clug-user@clug.org Subject: Re: OO in C (was Re: KDE crap) Message-ID: MIME-Version: 1.0 Content-Type: TEXT/PLAIN; charset=US-ASCII X-Keywords: X-UID: 884 Status: O X-Status: (I intended to send this to the group to begin with, but didn't. I've taken the opportunity to resend to fix the subject line.) On Thu, 30 Jul 1998, Jim Weirich wrote: > There are generally four pieces to the OO pie that needs to be there > in order to call it "Object Oriented" ... Abstraction, Encapsulation, > Inheritance, and Polymorphism.[0] > > . . . > > The major piece of that pie that distinguishes OO from non-OO (in my > opinion) is the presence of runtime polymorphism. Without > polymorphism, your are not doing OO designs or writing OO code. > > . . . > > In C it is easy to get abstraction, and with a bit of work, you can do > a fairly decent job of encapsulation. I've even seen inheritance done > in C (ever look at the Motif libraries? ... I'm not sure the effort was > worth it). > > But polymorphism is more difficult to manage. Generally its done with > function tables and relies on the programmer to do a lot of manual > initializations and some really awful looking casts.[5] Actually, it's not too bad. You don't have to do really awful-looking casts; you just have to do things like this: struct drawing_interface { int (*draw)(void); int (*moveto)(int x, int y); int (*rmoveto)(int dx, int dy); }; struct drawn_object { struct drawing_interface *ftbl; int *private_data; }; int f(struct drawn_object *x) { return x->ftbl->rmoveto(1, 1) && x->ftbl->draw(); } You can make private_data a void* if you want, and store class-specific information in a class-specific struct, which requires somewhat-ugly casts inside your private functions, but if you're willing to store your data in an int array, you don't even need to do that. You can even include some kind of inheritance (although I'm not sure how you do that without redeclaring all the methods you want to inherit) with a pointer to an object of a base class. Lots of the Linux kernel is written in something similar to the above style; the various things you can do with a file descriptor (read, write, ioctl, etc.) are mostly handled by function pointers in a struct file_ops, similar to the drawing_interface above. This provides easy polymorphism but no inheritance. How do you do inheritance cleanly? > But is it worth it? Probably not.[6] If I was stuck using a C as an > implementation language, I would use gobs of abstraction, a generous > portion of encapsulation, and skip the inheritence and polymorphism. Sometimes the advantages of run-time-bound polymorphism are too great to ignore. And it's not that hard to do it... > [3] Does the parenthesis in the smiley count as closing a nested > parenthetical comment?[4] I think I have seen it both ways. I've often pondered this myself. > [5] If someone is really interested, I would be happy to go into this > in more detail. It looks like you did. > [6] I actually did true polymorphism in C once. It was in a dialog > editor application. The programmer could define edit-able fields > which were handled according to 3 polymorphic functions: ToString, > FromString, and HandleKeyStroke. The To/FromString functions were > used to convert arbitrary data fields to strings for display in the > dialog box. The HandleKeyStroke would interpret the users keystrokes > as appropriate for each field, for example, numeric fields would > disable any keystroke except 0 thru 9. I used delgation (rather than > inheritance) to implement reuse (e.g. the numeric fields would filter > out all non-numeric keys as errors and delegated the numeric > keystrokes to the general string keystroke handler). Each field > object contained three function pointers that were called by the > dialog editor runtime. I handled the messy problem of making sure the > objects were properly initialized by writing a Dialog Compiler that > would read a dialog description and generate the C code for all the Ick. Why not just declare a "new_objecttype" function for each type of object (is it OK to call them classes? :) ), and then just not instantiate any objects of that class anywhere else than in the new_objecttype function? That way they're all properly initialized, because they're all initialized in the same place. Kragen