Wednesday, May 11, 2011

Replacing and upgrading the "new" operator

As promised in my previous post, I will now endeavour to replace and upgrade the functionality of C++'s new operator.

The new operator in C++ allocates enough memory for an instance of CLASS from the heap, and then calls the constructor for CLASS with this set to the address of the allocated heap memory. The upgraded replacement for new presented here provides the following services:

  1. Allocates memory for the instance from the heap.
  2. Calls constructor with address of instance to be initialized.
  3. Handles possible failures of both malloc() and the constructor
  4. In the case of constructor failure the destructor is called, and the memory is freed.

Recall the OOPinC prototype of the constructor for class "CLASS":

CLASS* CLASS_constructor(CLASS *self);

And now, a macro to provide new operator functionality in C:

#include <stdlib.h>
#define CLASS_create(p) \
  ((p)= (CLASS_constructor((p)=malloc(sizeof(CLASS))) ? \
  (p) : \
  ( p ? realloc(CLASS_destructor(p),0): 0)))

Which would be used like so:

CLASS *pClass;
CLASS_create(pClass);
if(!pClass) {
  /* error reporting and/or recovery */
}

or, more succinctly:

CLASS *pClass;
if(!CLASS_create(pClass)) {
  /* error reporting and/or recovery */
}

That's a lot to bite off at once, so let's break the macro down into components. The outermost structure is the ternary conditional operator which will branch differently based on the return value of CLASS_constructor(). In our case a return value of NULL means the constructor failed, otherwise the constructor was successful. The allocation of memory from the heap for our instance is accomplished by calling malloc(), which takes sizeof(CLASS) as the argument telling how much memory needs to be allocated. Note that the return of malloc() is stored in p for later reference within the macro.

If the constructor was successful initializing the memory referenced by p, then the value assigned to p is returned. If not, then there is a nested ternary conditional operator to deal with the two possible modes of failure:

  • In the case that malloc() was successful but the constructor failed, CLASS_destructor() is called with the value of p as the argument. CLASS_destructor() should be able to handle partially constructed instances of CLASS because that is usually the product of a failed constructor. Finally, the return of CLASS_destructor() is passed as the first argument to realloc(), with the second (size) argument set to 0. realloc() is used instead of free() because it returns void*, which is reconcilable with CLASS* and provides the required return for the non-null branch of the outer ternary conditional operator.
  • In the case that malloc() returns NULL, the macro itself returns NULL.

In my next post I will present the anatomy of an OOPinC destructor.

2 comments:

  1. Why did you choose to have CLASS_create() as a macro instead of a function?

    ReplyDelete
  2. @Chruck excellent question, thanks for asking. I chose a macro purely for convenience sake. Using a macro requires an entry in the '.h' file. Using a function would require a prototype in the '.h' file, and an implementation in the '.c' file.

    ReplyDelete