tags:

views:

1049

answers:

13

Possible Duplicates:
Can you write object oriented code in C?
Object Oriented pattern in C ?

I remember reading a while ago about someone (I think it was Linus Torvalds) talking about how C++ is a horrible language and how you can write object-oriented programs with C. On having had time to reflect, I don't really see how all object oriented concepts carry over into C. Some things are fairly obvious. For example:

  1. To emulate member functions, you can put function pointers in structs.
  2. To emulate polymorphism, you can write a function that takes a variable number of arguments and do some voodoo depending on, say, the sizeof the parameter(s)

How would you emulate encapsulation and inheritance though?

I suppose encapsulation could sort of be emulated by having a nested struct that stored private members. It would be fairly easy to get around, but could perhaps be named PRIVATE or something equally obvious to signal that it isn't meant to be used from outside the struct. What about inheritance though?

+2  A: 

the gtk and glib libraries use macros to cast objects to various types.
add_widget(GTK_WIDGET(myButton));
I can't say how it's done but you can read their source to find out exactly how it's done.

Chris H
the important thing is that it doesn't compile if you forget the macro.
Chris H
The GObject framework used by Glib/GTK is documented here: http://library.gnome.org/devel/gobject/unstable/
P-Nuts
They can get away with this very easily because they don't allow multiple inheritance. Regardless of what type you cast to, the base address of an object is always the same. "Inheritance" just becomes declaring a new struct where the first member is the base class and the new members for that class come afterwards. So you can cast to the base class and the address stays the same.
asveikau
`GTK_WIDGET(myButton)` translates to `((GtkWidget*)myButton)` plus a type checking call, unless you have type checking disabled, in which case it literally translates to just `((GtkWidget*)myButton)`.
pib
+1  A: 

For a great example of object-oriented programming in C, look at the source of POV-Ray from several years ago - version 3.1g is particularly good. "Objects" were struct with function pointers, of course. Macros were used to provide the core methods and data for an abstract object, and derived classes were structs that began with that macro. There was no attempt to deal with private/public, however. Things to be seen were in .h files and implementation details were in .c files, mostly, except for a lot of exceptions.

There were some neat tricks that I don't see how could be carried over to C++ - such as converting one class to a different but similar one on the fly just by reassigning function pointers. Simple for today's dynamic languages. I forgot the details; I think it might have been CSG intersection and union objects.

http://www.povray.org/

DarenW
Enforcing encapsulation is going to be hard in any "OO C" setup (you might prefer to just make it a matter of implicit convention, to save effort), but yeah, the basic premise is to have a bunch of function pointers in the object (or a pointer to a bunch of function pointers, or something like that) and call those.
fennec
@fennec: that's actually backwards, because c++ requires you to put implementation details of a class into a public header file, while c does not.
John Knoeller
+13  A: 

Have you read the "bible" on the subject? See Object Oriented C...

Kornel Kisielewicz
+1: Nice link to PDF
tur1ng
@tur1ng -- by accident -- I have the PDF extension in Chrome installed :>
Kornel Kisielewicz
+1  A: 

An interesting bit of history. Cfront, the original C++ implementation output C code and then requires a C compiler to actually build the final code. So, anything that could be expressed in C++ could be written as C.

R Samuel Klatchko
True. However the C code that cfront outputted was not code that any human programmer would ever have written (ex cfront user speaking).
anon
@Neil: True. However it still means that any C++ code can be trivially converted to C code - which is what the OP was wondering about and probably what Linus alluded to.
slebetman
+17  A: 

You can implement polymorphism with regular functions and virtual tables (vtables). Here's a pretty neat system that I invented (based on C++) for a programming exercise: alt text

The constructors allocate memory and then call the class's init function where the memory is initialized. Each init function should also contain a static vtable struct that contains the virtual function pointers (NULL for pure virtual). Derived class init functions call the superclass init function before doing anything else.

A very nice API can be created by implementing the virtual function wrappers (not to be confused with the functions pointed to by the vtables) as follows (add static inline in front of it, if you do this in the header):

int playerGuess(Player* this) { return this->vtable->guess(this); }

Single inheritance can be done by abusing the binary layout of a struct: alt text

Notice that multiple inheritance is messier as then you often need to adjust the pointer value when casting between types of the hierarchy.

Other type-specific data can be added to the virtual tables as well. Examples include runtime type info (e.g. type name as a string), linking to superclass vtable and the destructor chain. You probably want virtual destructors where derived class destructor demotes the object to its super class and then recursively calls the destructor of that and so on, until the base class destructor is reached and that finally frees the struct.

Encapsulation was done by defining the structs in player_protected.h and implementing the functions (pointed to by the vtable) in player_protected.c, and similarly for derived classes, but this is quite clumsy and it degrades performance (as virtual wrappers cannot be put to headers), so I would recommend against it.

Tronic
+1 for the pretty pictures.
GregS
+1  A: 

One way to handle inheritance is by having nested structs:

struct base
{
    ...
};

void method_of_base(base *b, ...);

struct child
{
    struct base base_elements;
    ...
};

You can then do calls like this:

struct child c;
method_of_base(&c.b, ...);
R Samuel Klatchko
+5  A: 

How would you emulate encapsulation and inheritance though?

Actually, encapsulation is the easiest part. Encapsulation is a design philosophy, it has nothing at all to do with the language and everything to to with how you think about problems.

For example, the Windows FILE api is completely encapsulated. When you open a file, you get back an opaque object that contains all of the state information for the file 'object'. You hand this handle back to each of the file io apis. The encapsulation is actually much better than C++ because there is no public header file that people can look at and see the names of your private variables.

Inheritance is harder, but it isn't at all necessary in order for your code to be object oriented. In some ways aggregation is better than inheritance anyway, and aggregation is just as easy in C as in C++. see this for instance.

In response to Neil see Wikipedia for an explanation of why inheritance isn't necessary for polymorphism.

Us old-timers wrote object oriented code years before C++ compilers were available, it's a mind-set not a tool-set.

John Knoeller
If you haven't got inheritance, you haven't got run-time polymorphism, and you haven't got OO.
anon
@Neil: I don't see you making mistakes often, but in this you are completely wrong. inheritance is not a _necessary_ feature of OO. you can get polymorphism through aggregation/delegation.
John Knoeller
@John You wouldn't consider Ada83 object-oriented, yet it has all of the features except inheritance and polymorphism. I concur with Neil. To say otherwise is to completely abandon the entire purpose the OOP paradigm was invented: code reuse.
San Jacinto
the most re-used piece of code ever is the _standard_ c-runtime library, while most c++ code is actually viral, you can't use just a piece of it without sucking in the whole enchalada.
John Knoeller
@San Jacinto: It's entirely possible to write OO code in ADA, but no, don't consider it an OO language. I don't consider C++ an OO language either - too much leakage from it's systems programming roots.
John Knoeller
+1  A: 

Apple's C-based CoreFoundation framework was actually written so that its "objects" could double as objects in Objective-C, an actual OO language. A fairly large subset of the framework is open source on Apple's site as CF-Lite. Might be a useful case study in a major OS-level framework done this way.

Chuck
+1 for a large real-world example.
slebetman
A: 

You might want to look at Objective-C, that's pretty much what it does. It's just a front-end that compiles Objective-C OO code to C.

Bill K
+2  A: 

Take a look at the way the VFS layer works in the Linux kernel for an example of an inheritance pattern. The file operations for the various filesystems "inherit" a set of generic file operations functions (eg generic_file_aio_read(), generic_file_llseek()...), but can override them with their own implementations (eg. ntfs_file_aio_write()).

caf
+1  A: 

From a little bit higher altitude and considering the problem rather more open-minded than as the OOP mainstream may suggest, Object-Oriented Programming means thinking about objects as of data with associated functions. It does not necessarily mean an function has to be physically attached to an object as it is in popular languages which support paradigm of OOP, for instance in C++:

struct T
{
   int data;
   int get_data() const { return data; }
};

I would suggest to take a closer look at GTK+ Object and Type System. It is a brilliant example of OOP realised in C programming language:

GTK+ implements its own custom object system, which offers standard object-oriented features such as inheritance and virtual function

The association can also be contractual and conventional.

Regarding encapsulation and data hiding techniques, popular and simple one may be Opaque Pointer (or Opaque Data Type) - you can pass it around but in order to load or store any information, you have to call associated function which knows how to talk to the object hidden behind that opaque pointer.

Another one, similar but different is Shadow Data type - check this link where Jon Jagger gives excellent explanation of this not-so-well-known-technique.

mloskot
+1  A: 

Definitely look at Objective-C.

typedef struct objc_object {
    Class isa;
} *id;

typedef struct objc_class {
    struct objc_class *isa;
    struct objc_class *super_class
    const char *name;
    long version;
    long info
    long instance_size;
    struct objc_ivar_list *ivars;
    struct objc_method_list **methodLists;
   struct objc_cache *cache;
   struct objc_protocol_list *protocols;
} *Class;

As you can see, inheritance information, along with other details is held in a class struct (conveniently the class can also be treated as an object).

Objective-C suffers in the same manner as C++ with encapsulation in that you need to declare your variables publicly. Straight C is much more flexible in that you can just return void pointers that only your module has internal access to, so in that respect encapsulation is much better.

I once wrote a basic OO style C paint program as part of a graphics course - I didn't go as far as the class declaration, I simply used a vtable pointer as the first element of the struct and implemented hand-coded inheritance. The neat thing about playing around with vtables at such a low level is that you can change class behaviour at runtime by changing a few pointers, or change on objects class dynamically. It was quite easy to create all sorts of hybrid objects, fake multiple inheritance, etc.

Duncan
+1  A: 

Nice article & discussion regarding Objective-C here:

http://cocoawithlove.com/2009/10/objective-c-niche-why-it-survives-in.html

higgo
Several times in the past I thought it might be fruitful to study Objective-C. This article piques my interest enough to actually do so now.
DarenW