Oop in C
Oop in C
Oop in C
Can someone please share a set of nif ty preprocessor hacks (ANSI C89/ISO C90 compatible please) which enable some kind of ugly (but usable) objectorientation in C? I am f amiliar with a f ew dif f erent object-oriented languages, so please don't respond with answers like "Learn C++!". I hav e read "ObjectOriented Programming With ANSI C" (beware: pdf) and sev eral other interesting solutions, but I'm mostly interested in y ours :-)!
Can I respond to learn D and use the c compatible abi f or where y ou really need C. digitalmars.com/d Tim Matthews Jan 6 '09 at 11:53 Not really . I work with some embedded sy stems which only really hav e a C compiler av ailable. anon Jan 6 '09 at 21:51 @Dinah: Thank y ou f or the "See also". That post was interesting. anon Jan 6 '09 at 22:20 The interesting question seems to be why would y ou want a pre-processor hack of OOP on C. Caly th Jan 8 '09 at 19:13 @Caly th: I f ind that OOP is usef ul and "I work with some embedded sy stems which only really hav e a C compiler av ailable" (f rom abov e). Moreov er, don't y ou f ind nif ty preprocessor hacks interesting to look at? anon Jan 9 '09 at 21:09
f eedback
10 Answers
C Object Sy stem (COS) sounds promising (it's still in alpha v ersion). It tries to keep minimal the av ailable concepts f or the sake of simplicity and f lexibility : unif orm object oriented programming including open classes, metaclasses, property metaclasses, generics, multimethods, delegation, ownership, exceptions, contracts and closures. There is a draf t paper (PDF) that describes it. Exception in C is a C89 implementation of the TRY -CATCH-FINALLY f ound in other OO languages. It comes with a testsuite and some examples. Both by Laurent Deniau, which is working a lot on OOP in C. answered Jan 6 '09 at 7:51 philippe
10.4k
40
f eedback
I would adv ise against preprocessor (ab)use to try and make C sy ntax more like that of another more object-oriented language. At the most basic lev el, y ou just use plain structs as objects and pass them around by pointers: struct monkey { float age; bool is_male; int happiness; }; void monkey_dance(struct monkey *monkey) { /* do a little dance */ } To get things like inheritance and poly morphism, y ou hav e to work a little harder. Y ou can do manual inheritance by hav ing the f irst member of a structure be an instance of the superclass, and then y ou can cast around pointers to base and deriv ed classes f reely : struct base { /* base class members */ };
struct derived { struct base super; /* derived class members */ }; struct derived d; struct base *base_ptr = (struct base *)&d; // upcast struct derived derived_ptr = (struct derived *)base_ptr; // downcast void poly morphism (i.e. v irtual f unctions), To get derived1_init(struct derived1 *d) y ou use f unction pointers, and optionally f unction pointer tables, also known as v irtual tables or v tables: { d->super.vtable = &derived1_vtable; /* init base members d->super.foo */ /* init derived1 members d->foo */ } struct derived2 { struct base super; /* derived2 members */ }; void derived2_dance(struct derived2 *d) { /* implementation of derived2's dance function */ } void derived2_jump(struct derived2 *d, int how_high) { /* implementation of derived2's jump function */ } struct base_vtable derived2_vtable = { &derived2_dance, &derived2_jump }; void derived2_init(struct derived2 *d) { d->super.vtable = &derived2_vtable; /* init base members d->super.foo */ /* init derived1 members d->foo */ } int main(void) { /* OK! We're done with our declarations, now we can finally do some polymorphism in C */ struct derived1 d1; derived1_init(&d1); struct derived2 d2; derived2_init(&d2); struct base *b1_ptr = (struct base *)&d1; struct base *b2_ptr = (struct base *)&d2; base_dance(b1_ptr); /* calls derived1_dance */ base_dance(b2_ptr); /* calls derived2_dance */ base_jump(b1_ptr, 42); /* calls derived1_jump */ base_jump(b2_ptr, 42); /* calls derived2_jump */ return 0; } And that's how y ou do poly morphism in C. It ain't pretty , but it does the job. There are some sticky issues inv olv ing pointer casts between base and deriv ed classes, which are saf e as long as the base class is the f irst member of the deriv ed class. Multiple inheritance is much harder - in that case, in order to case between base classes other than the f irst, y ou need to manually adjust y our pointers based on the proper of f sets, which is really tricky and error-prone. Another (tricky ) thing y ou can do is change the dy namic ty pe of an object at runtime! Y ou just reassign it a new v table pointer. Y ou can ev en selectiv ely change some of the v irtual f unctions while keeping others, creating new hy brid ty pes. Just be caref ul to create a new v table instead of modif y ing the global v table, otherwise y ou'll accidentally af f ect all objects of a giv en ty pe. edited Jan 6 '09 at 9:46 philippe answered Jan 6 '09 at 5:09 Adam Rosenf ield 1 9 40
10.4k
79.2k
11
115
211
Adam, the f un of changing the global v table of a ty pe is to simulate duck-ty ping in C. :) jmucchiello Sep 28 '09 at 14:59 Now I pity C++... Well of course the C++ sy ntax is clearer, but since it's not a triv ial sy ntax, I'm mitigated. I wonder if something hy brid between C++ and C could be achiev ed, so v oid* would still be v alid castable ty pe. The part with struct derived {struct base super;}; is obv ious to guess how it works, since by the by tes order it's correct. gokoon Jan 4 at 15:41
f eedback
(ripped f rom How to write production C code? [closed]) I once worked with a C library that was implemented in a way that struck me as quite elegant. They had written, in C, a way to def ine objects, then inherit f rom them so that they were as extensible as a C++ object. The basic idea was this: Each object had its own f ile Public f unctions and v ariables are def ined in the .h f ile f or an object Priv ate v ariables and f unctions were only located in the .c f ile To "inherit" a new struct is created with the f irst member of the struct being the object to inherit f rom Inheriting is dif f icult to describe, but basically it was this: struct vehicle { int power; int weight; } Then in another f ile: struct van { struct vehicle base; int cubic_size; }
Then y ou could hav e a v an created in memory , and being used by code that only knew about v ehicles: struct van my_van; struct vehicle *something = &my_van; vehicle_function( something ); It worked beautif ully , and the .h f iles def ined exactly what y ou should be able to do with each object. answered Jan 6 '09 at 4:28 Kiev eli
4,714
14
28
I'd like to know more about how this worked :) J Cooper Jan 6 '09 at 4:48 I really like this solution, except that all of the "object"'s internals are public. Sof tware Monkey Jan 6 '09 at 5:18
@Sof tware Monkey : C has no access control. The only way to hide implementation details is to interact through opaque pointers, which can get pretty painf ul, since all f ields would need to be accessed through accessor methods which probably can't be inlined. Adam Rosenf ield Jan 6 '09 at 5:26 @Adam: Compilers supporting link-time optimizations will inline them just f ine... Christoph Jan 6 '09 at 13:01
If y ou do this, y ou should also ensure that all the f unctions in the .c f ile that are not def ined as public are def ined as static so they don't end up as named f unctions in y our object f iles. That ensures no one can f ind their names in the link phase. jmucchiello Sep 28 '09 at 15:01
The GNOME desktop f or Linux is written in object-oriented C, and it has an object model called "GObject" which supports properties, inheritance, poly morphism, as well as some other goodies like ref erences, ev ent handling (called "signals"), runtime ty ping, priv ate data, etc. It includes preprocessor hacks to do things like ty pecasting around in the class hierarchy , etc. Here's an example class I wrote f or GNOME (things like gchar are ty pedef s): Class Source Class Header Inside the GObject structure there's a GTy pe integer which is used as a magic number f or GLib's dy namic ty ping sy stem (y ou can cast the entire struct to a "GTy pe" to f ind it's ty pe). edited Jan 7 '09 at 11:50 answered Jan 6 '09 at 5:06 James Cape
341
unf ortunately , the read me/tutorial f ile (wiki link) is not working and there is only ref erence manual f or that(i am talking about GObject and not GTK). please prov ide some tutorial f iles f or the same ... FL4SOF Jan 6 '09 at 5:22 See stackov erf low.com/questions/500501/ f mark Nov 6 '10 at 12:35
Yes
No
f f mpeg (a toolkit f or v ideo processing) is written in straight C (and assembly language), but using an object-oriented sty le. It's f ull of structs with f unction pointers. There are a set of f actory f unctions that initialize the structs with the appropriate "method" pointers. answered Jan 6 '09 at 4:31 Mr Fooz
8,479
14
37
I'll take a look at the source in a little while. I'll respond with my impressions of it either tonight or sometime tomorrow. anon Jan 6 '09 at 4:48 i don't see any f actory f unctions in it(f f mpeg), rather it doesnt seem to be using poly morphism/inheritance ( triv ial way suggested abov e). FL4SOF Jan 6 '09 at 5:39 av codec_open is one f actory f unction. It stuf f s f unction pointers into a AVCodecContext struct (like draw_horiz_band). If y ou look at FF_COMMON_FRAME macro usage in av codec.h, y ou'll see something akin to inheritance of data members. IMHO, f f mpeg prov es to me that OOP is best done in C++, not C. Mr Fooz Jan 6 '09 at 23:41
f eedback
Slightly of f topic but the original C++ compiler, c-f ront, compiled C++ to C and then to assembler. Preserv ed here answered Aug 5 '09 at 9:55 community wiki zebrabox
I'v e actually seen it bef ore. I believ e it was a nice piece of work. anon Aug 5 '09 at 10:11 @Anthony Cuozzo : Stan Lippman wrote a great book called 'C++ - Inside the object model' where he related a lot of his experiences and design decisions in writing and maintaining c-f ront. It's still a good read and helped me immensely when transitioning f rom C to C++ many y ears back zebrabox Aug 5 '09 at 10:40
f eedback
I used to do this kind of thing in C, bef ore I knew what OOP was. Following is an example, which implements a data-buf f er which grows on demand, giv en a minimum size, increment and maximum size. This particular implementation was "element" based, which is to say it was designed to allow a list-like collection of any C ty pe, not just a v ariable length by te-buf f er. The idea is that the object is instantiated using the xxx_crt() and deleted using xxx_dlt(). Each of the "member" methods takes a specif ically ty ped pointer to operate on. I implemented a linked list, cy clic buf f er, and a number of other things in this manner. I must conf ess, I hav e nev er giv en any thought on how to implement inheritance with this approach. I imagine that some blend of that of f ered by Kiev eli might be a good path. dtb.c: #include <limits.h> #include <string.h> #include <stdlib.h> static void dtb_xlt(void *dst, const void *src, vint len, const byte *tbl); DTABUF *dtb_crt(vint minsiz,vint incsiz,vint maxsiz) { DTABUF *dbp; if(!minsiz) { return NULL; } if(!incsiz) { incsiz=minsiz; } if(!maxsiz || maxsiz<minsiz) { maxsiz=minsiz; } if(minsiz+incsiz>maxsiz) { incsiz=maxsiz-minsiz; } if((dbp=(DTABUF*)malloc(sizeof(*dbp))) == NULL) { return NULL; } memset(dbp,0,sizeof(*dbp)); dbp->min=minsiz; dbp->inc=incsiz; dbp->max=maxsiz; dbp->siz=minsiz; dbp->cur=0; if((dbp->dta=(byte*)malloc((vuns)minsiz)) == NULL) { free(dbp); return NULL; } return dbp; } DTABUF *dtb_dlt(DTABUF *dbp) { if(dbp) { free(dbp->dta); free(dbp); } return NULL; } vint dtb_adddta(DTABUF *dbp,const byte *xlt256,const void *dtaptr,vint dtalen) { if(!dbp) { errno=EINVAL; return -1; } if(dtalen==-1) { dtalen=(vint)strlen((byte*)dtaptr); } if((dbp->cur + dtalen) > dbp->siz) { void *newdta; vint newsiz; if((dbp->siz+dbp->inc)>=(dbp->cur+dtalen)) { newsiz=dbp->siz+dbp->inc; } else { newsiz=dbp->cur+dtalen; } if(newsiz>dbp->max) { errno=ETRUNC; return -1; } if((newdta=realloc(dbp->dta,(vuns)newsiz))==NULL) { return -1; } dbp->dta=newdta; dbp->siz=newsiz; } if(dtalen) { if(xlt256) { dtb_xlt(((byte*)dbp->dta+dbp->cur),dtaptr,dtalen,xlt256); } else { memcpy(((byte*)dbp->dta+dbp->cur),dtaptr,(vuns)dtalen); } dbp->cur+=dtalen; } return 0; } static void dtb_xlt(void *dst,const void *src,vint len,const byte *tbl) { byte *sp,*dp; dtb.h typedef _Packed struct { vint min; vint inc; vint max; vint siz; vint cur; void *dta; } DTABUF; #define dtb_dtaptr(mDBP) #define dtb_dtalen(mDBP) DTABUF DTABUF vint vint vint vint void
/* initial size /* increment size /* maximum size /* current size /* current data length /* data pointer
*/ */ */ */ */ */
(mDBP->dta) (mDBP->cur)
*dtb_crt(vint minsiz,vint incsiz,vint maxsiz); *dtb_dlt(DTABUF *dbp); dtb_adddta(DTABUF *dbp,const byte *xlt256,const void *dtaptr,vint dtalen); dtb_addtxt(DTABUF *dbp,const byte *xlt256,const byte *format,...); dtb_rmvdta(DTABUF *dbp,vint len); dtb_reset(DTABUF *dbp); *dtb_elmptr(DTABUF *dbp,vint elmidx,vint elmlen);
PS: v int was simply a ty pedef of int - I used it to remind me that it's length was v ariable f rom platf orm to platf orm (f or porting). edited Aug 8 '09 at 3:04 answered Jan 6 '09 at 5:14 Sof tware Monkey
15.5k
38
71
holy moly , this could win an obf uscated C contest! i like it! :) banister Jul 29 '09 at 19:58
f eedback
If y ou think of methods called on objects as static methods that pass an implicit ' this ' into the f unction it can make thinking OO in C easier. For example: String s = "hi"; System.out.println(s.length()); becomes:
string s = "hi"; printf(length(s)); // pass in s, as an implicit this Or something like that. answered Jan 6 '09 at 5:04 jjnguy
34.9k
71
148
@Artelius: Sure, but sometimes the obv ious is not, until it's stated. +1 f or this. Sof tware Monkey Jan 6 '09 at 5:22
f eedback
f or me object orientation in C should hav e these f eatures : 1) encapsulation and data hiding ( can be achiev ed using structs/opaque pointers) 2) inheritance and support f or poly morphism ( single inheritance can be achiev ed using structs - make sure abstract base is not instantiable) 3) constructor and destructor f unctionality ( not easy to achiev e) 4) ty pe checking (at least f or user def ined ty pes as C doesn't enf orce any ) 5) ref erence counting ( or some thing to implement RAAI) 6) limited support f or exception handling (setjmp and longjmp ) on top of abov e it should rely on ANSI/ISO specif ications and should not rely on compiler specif ic f unctionality . answered Jan 6 '09 at 5:47 FL4SOF
322
16
For number (5) - Y ou can't implement RAII in a language without destructors (which means RAII is not a compiler-supported technique in C or Jav a). Tom Jan 6 '09 at 6:05 constructors and destructors can be written f or c based object - i guess GObject does it. and of course RAAI ( it is not straight f orward, may be ugly and need not be pragmatic at all) - all i was looking is to identif y C based semantics to acheiv e the abov e. FL4SOF Jan 6 '09 at 6:44 C doesn't support destructors. Y ou hav e to ty pe something in order to make them work. That means they don't clean up themselv es. GObject doesn't change the language. Tom Jan 7 '09 at 5:25
f eedback
If I were going to write OOP in C I would probably go with a pseudo-PIMPL design. Instead of passing pointers to structs, y ou end up passing pointers to pointers to structs. This makes the content opaque and f acilitates poly morphism and inheritance. The real problem with OOP in C is what happens when v ariables exit scope. There's no compiler generated destructors and that can cause issues. MACROS can possibly help but it is alway s going to be ugly to look at. answered Jan 6 '09 at 8:15 jmucchiello
6,934
11
28
f eedback
question f eed