Some days ago I read an article about goto heresy that triggered me to write about my personal experience with the infamous goto instruction in C. The only reason I found for employing goto in C was error handling. Valentino provided me with a good idea on which I elaborate a bit. Thanks to this idea you can achieve a passable approximation of RAII in C.
It is not free (beer sense) as it is in C++ or other languages that support automatic object destruction, but if you stick to a set of IMHO acceptable conventions you should be happy with it. Shouldn’t these conventions fit for you, you may easily bend the solution to satisfy your taste.
Before delving into the technical description of the idea I am going to list the conventions that are requested for the idea to work.
First class names have to be defined with the typedef instruction. E.g.
Then each class needs a constructor named as the class name with a trailing “_ctor”. In the same way the destructor has a trailing “_dtor”.
The first argument of the constructor is a pointer to the object to construct. Moreover the constructor returns true if the operation has been successful or false in case of construction failure.
It is up to the constructor to clean up in case of failure and not to leak any resources.
In the same way the destructor has a single argument – the pointer to the object to destruct.
By the way, by constructing I mean to receive a chunk of raw memory and turn it into a well formed, invariant-ready, usable and valid object. It has nothing to do with memory allocation – memory is provided by the code that calls the constructor.
The destructor does the opposite – takes a valid object and by freeing the associated resources makes it a useless bunch of raw bytes, ready to be recycled by someone else.
Now the idea is simple (as most of them after you know) – you need a way to keep track of what you construct so that when an error occurs you can go back and call destructors for each object already built.
Since you don’t know how many objects are going to be constructed the data structure that fit best is the linked list. And, if you are clever enough you may avoid the dynamic allocation at all by employing cleverly crafted node names.
When an object is successfully built a node of the list is created. Inside the node the pointer to the built object is stored along with the pointer to the destructor.
You know which is the destructor because you have the object type.
When a constructor fails the execution jumps (via a goto) to the error handling trap. The trap simply sweeps the linked list and processes each node by calling the destructor on the object.
Thanks to the C preprocessor the implementation is not so convoluted.
#define RAII_INIT typedef void DtorFn( void* );
struct DtorNode* next;
* dtorHead__ = NULL
#define RAII_CTOR( x__, T__, ... )
RAII_CTOR_WITH_LINE( __LINE__, x__, T__, __VA_ARGS__ )
#define RAII_CTOR_WITH_LINE( L__, x__, T__, ... )
struct DtorNode dtor_##T__##_##L__;
if( T__##_ctor( x__, __VA_ARGS__ ) )
dtor_##T__##_##L__.dtor = (DtorFn*)T__##_dtor;
dtor_##T__##_##L__.object = x__;
dtor_##T__##_##L__.next = dtorHead__;
dtorHead__ = &dtor_##T__##_##L__;
while( dtorHead__ != NULL )
dtorHead__->dtor( dtorHead__->object );
dtorHead__ = dtorHead__->next;
RAII_INIT the mechanism by defining the type of the linked list node and the pointer to the head of the list. Note that a single link list is enough since I want to have FIFO behavior (the first constructed object is the last to be destroyed). Also the name of the type will be local to the function where this macro will be instantiated, therefore it there won’t be collision in the global namespace.
RAII_CTOR macro is used to invoke an object constructor. The real work is done by the RAII_CTOR_WITH_LINE, that accepts the same arguments as RAII_CTOR plus the line where the macro is expanded. The line is needed to create unique node identifiers within the same function.
RAII_CTOR needs the name of the object type in order to be able to build the name of the constructor and the name of the destructor. From these information the macro is able to call the constructor and add a node to the destruction list if successful or jump to the destructor trap if the constructor fails.
RAII_TRAP is the trap, to be located at the end of the function. It intercepts a constructor failure and performs the required destruction by scanning the list.
In order to use the macros you lay out the function according to the following canvas:
bool f( /* whatever */ )
// some code
RAII_CTOR( ... ); // one or more ctor(s)
return true; // everything was fine
RAII_TRAP; // code below is executed only in case of error.
As you see the trap performs the destruction, but leaves you the space to add your own code (in the example the “return false;” statement).
So far so good, but you may argue that memory allocation and file open/close already have their conventions set in the standard library that don’t fit my macro requirements.
Don’t worry, it is quite straightforward to hammer malloc/free and fopen/fclose in the _ctor/_dtor schema. It is as simple as:
#define malloc_ctor(X__,Y__) (((X__) = malloc( Y__ )) != NULL)
#define malloc_dtor free
#define fopen_ctor(X__,NAME__,MODE__) (((X__) = fopen( NAME__, MODE__ ))!= NULL )
#define fopen_dtor fclose
Here is an example of how the code that employs my RAII macros could look:
f( void )
RAII_CTOR( memory, malloc, 100 );
RAII_CTOR( file, fopen, "zippo", "w" );
RAII_CTOR( &foo, Foo, 0 );
This code has some great advantages over the solutions I presented in my old post. First it has no explicit goto (the goto is hidden, as much as it is in any other structured statement). Then you don’t have to care about the construction order and to explicit write the destructor calls.
Though there are some drawbacks. First the linked list has an overhead that I don’t think the optimizer will be able to avoid. The space overhead is 1 function pointer and 2 data pointers (plus alignment padding) for each constructed object. This space is taken from the stack, but it is completely released when the function returns.
The code requires a C99 compliant, or, at least a compiler that allows you to declare variables anywhere in the code (and not just at the block begin). I think that the function pointer and argument pointer juggling are a bit on (or maybe beyond) the edge of the standard compliance. I tested the code on a PC, but maybe that it fails on more exotic architectures.
So, what do you think?