Tuesday, February 24, 2009

Object Oriented C

In my first job, the project we worked on was 100% C code. However, it was object oriented C. This was led by our colleague Chris Westin. As we were fond of pointing out, there is a difference between Object oriented languages and Object oriented programming. You can apply OOP concepts (given appropriate primitives) in any language. Here's a table where I'll record a few of these...
Runtimepolymorphism Achievable by using function pointers, and syntactic sugar is done with clever macros.
Link-time polymorphism You probably do this already but don't define it as such; for instance, if you implement a function defined in a header differently on different platforms, you can consider this polymorphism. The function is different on Windows vs Linux. Another example might be a plugin for a browser. If you want it to run in Firefox and Opera, you might be able to get the core of it to call your own abstracted calls to the browser; the implementation thereof is determined at link-time.
Abstraction This is more a matter of design than implementation, and thus is applicable to any language.
Information hiding Again, this is a design issue. But the mechanism that is often used to achieve this is referred to as encapsulation.
Encapsulation This can be done in different ways, but usually the most effective technique is opaque types. Again, with clever data structures and macros, this can be combined with the above Runtime polymorphism to construct objects that feel like C++, or can even co-exist with C++.
Exceptions You can simulate some of this using setjmp/longjmp, but this can only go so far; the compiler doesn't know what's going on, so if you have atry/catch block that's really two macros doing housekeeping on thetry/catch data structures, and then you return or break orcontinue out of the middle of that, there's nothing to stop you, and you've corrupted your try/catch data structures. A better method to use is to create an error data structure that can contain more information (like __FILE__ and __LINE__) than a simple int error code. This doesn't get you the magic stack unwinding, but at least it can be more informative than -1.
Now you might be asking "why not use C++?" There are lots of answers to this, but here are a few:
  • Fragile binary interface problem or Fragile base class problem
    This is a real problem for deployment of C++ code, and likely an important driver early on for Microsoft to develop COM. If you ship C++ objects in a shared library, you can't do so without being extremely careful about what's exposed in the header file.
  • Windows Debug Heap
    Similarly, on Windows you must be careful with memory management. You can't cross allocations/deallocations across module boundaries in Windows, because that would be very bad. This can easily happen in C++ if you do allocations in the header file, and then deallocation in the implementation file (or vice-versa). You might be mixing memory heaps which will cause your app to crash.
  • Incompatible behaviors across compilers or even compiler versions.
    Certainly early on different compilers or even different versions of the same compiler can generate code that is incompatible in terms of things like throwing/catching exceptions, or name mangling. This might not have been a problem for some time (I haven't checked), but is indicative of C++'s lack of an ABI.
  • C++ Standard library lacks ABI
    Similar to the above points, if you use a certain version of compiler/C++ Standard library in your shared object, you cannot share those data types with another shared object or application that uses a different version of compiler/C++ Standard library.
  • C++0x doesn't appear to address any of the ABI issues that are so well known in C++. If I'm wrong, please correct me. Bjarne's C++0x FAQ (or his C++ FAQ) doesn't even mention the word "binary"; although the word "ABI" is used, but in reference to the GC system.
  • Lack of a "platform".
    This is a common criticism, which of course C and other languages share. If you want to acquire a mutex, you have to do it differently depending on what platform you're on. Java and other more modern languages include ways to do this, and many many other things. C++0x and its standard library seem to address at least some of this...
Don't get me wrong, C++ is an extremely useful language that I use in lots of projects, but you have to know its limitations, in addition to mastering its use. I just wish that the most glaring deficiency, binary compatibility, was address in C++0x.