Perception
Object oriented programming is a hoax. A good one, I’ll admit; but it’s nothing more than some glorified hand waving and a leap of faith. Say we have an imaginary C library called the Nifty API. Following the C tradition, typical API usage might look something like this…
Listing A:
NIFTY* handle = nifty_init(); nifty_action( handle, 37 ); nifty_other_action( handle, "testing", fp ); nifty_destroy( handle );
Now, it’s easy to guess what the compiler might be doing when it gets to Listing A. There is some library call or resource allocation happening in the nifty_init() call, which returns the address of a custom data structure (a NIFTY pointer). The address of the NIFTY pointer then gets carted around by the application developer and passed into every other Nifty library call. Then, we tell the Nifty library to deallocate those session resources by a call to nifty_destroy() (to which we also pass the NIFTY resource handle).
If the Nifty developers had built instead a native C++ (read: object oriented) API, it’s not too difficult to imagine what such a beast might look like.
Listing B:
Nifty* handle = new Nifty(); handle->action( 37 ); handle->otherAction( "testing", fp ); delete handle;
Listing B is nearly the exact same thing as listing A, and here’s why: Object oriented programming is just a scope trick. Essentially, classes are pseudo-namespaces, in which you can define data and functions (as in any namespace), and the compiler just does some extra legwork for you. Conceptually*, what the compiler does, in Listing B, is a “translation” to the following.
Listing C:
Nifty* handle = Nifty::Nifty( (Nifty*)malloc( sizeof(Nifty) ) ); Nifty::action( handle, 37 ); Nifty::otherAction( handle, "testing", fp ); Nifty::~Nifty( handle );
The call to the Nifty constructor in B.1 takes no arguments, but C.1 passes the result of a malloc() into it. Over lines C.2 and C.3, the reference to the handle gets moved from object syntax into the first parameter slot, and call the methods with the scope resolution operator. C.4 directly calls the Nifty destructor – again, with an extra parameter! What’s going on here? Some of you will already have guessed it. The legendary this pointer. When you instantiate a new class object, the compiler isn’t wasting it’s time copying the code for the class methods over and over – it keeps a global copy of that. Instead, it uses a hidden pointer that gets passed to every nonstatic method call. The this pointer. That’s how the compiler distinguishes between handle->action( 37 ) and other_handle->action( 37 ).
Static members and methods are easy. Let’s say you define a class with ONLY static members and methods, like so…
Listing D:
class Bar { public: static int a; static char b; static void fun( void ); };
There is no difference between this code, and if we were to convert this class into a namespace like this…
Listing E:
namespace Bar { int a; char b; void fun( void ); };
Ignoring the using keyword, there is absolutely no difference, syntactically or semantically, between the usage of Listings D and E. None at all. Think about it.
The only differences between classes and static namespaces are with respect to nonstatic methods and members. For nonstatic members and methods, the compiler will basically generate a struct containing the members (the address of which gets used for the this pointer), and for all method calls, performs the aforementioned “translation.” Voila! Procedurally, we’ve just reconstructed the object oriented paradigm. And it was just some hand waving on part of the compiler. A glorified mind trick, and nothing more.
However, this “glorified mind trick” is not to be underestimated. It has caused an industry-wide paradigm shift, and has itself paved the way for ever more abstract programming techniques. And this is the whole point of my example (besides, perhaps, to broaden your view of the object oriented pattern): to reinforce the age old “perception is reality” mantra. When I write something using the object oriented paradigm, I don’t worry that all that craziness is going on behind the scenes. I honestly don’t really care that it’s being turned into procedural code for me. I think in terms of objects, and I code in terms of objects, and it makes life easier all the way around. And that’s the point – while I’m coding objects, I’m perceiving objects, and while I perceive them, they are the reality of my code.
If your website is down, users don’t care that it’s actually the database that’s down, and not really the website. They won’t care whether it’s the database, the nameservers, or little green men swinging from the ladder racks in your network closet. If they can’t use it, it’s broken. And it doesn’t matter if the blame lies with you, them, or somebody in between.
*I am by no means a compiler developer, nor do have I studied compiler design theory in any depth yet. My claims in this post are merely arguments formed from years’ experience battling with (and against) GCC. My code (specifically regarding the “refactoring” that occurs between Listings B and C) is merely illustrational. However, at the end of the day, everything is machine code, twiddling bits on individual registers. So, whether at a high level or low, our wonderful objects do get deconstructed at some point. This is simply my theory as to how this gets done in C++ with the GNU Compiler Collection.