Next: , Previous: Objects and Classes, Up: Top


3 Exception handling

However the exception handling from the user's point of view is very similar to the exception handling in C++, there are very significant differences that you must keep in mind! In ooc the key differences are:


Being able to use the exceptions you must include the exception.h header file, and call ooc_init_class( Exception ); at the very beginning of your code! Because ooc uses the exceptions internally, you must always initialize the Exception class in your code before using any other ooc features!

3.1 Throwing an exception

Throwing an exception is very easy with the ooc_throw() instruction. The parameter of the ooc_throw() is any Object. The object is "owned" by the exception handling mechanism and will be deleted by it, so never delete it yourself, and never throw an object, that you would like to use later. In practice I recommend throwing newly created objects of Exception class or its subclasses, like:

     if( error )
         ooc_throw( str_exception_new( str_error_code ));

You can use ooc_rethrow() as well for passing the actual exception to the caller, but only inside a catch() or in a catch_any block.

3.2 Catching an exception

You can catch the thrown exceptions with the catch() or catch_any blocks. The catch( Class ) block catches the objects of the specified class or its parent (super-) classes. The caught object is stored in a variable called Exception exception, that is automatically defined and can be used only within the catch block.


The catch_any block catches all exceptions that were not handled by the earlier catch blocks. It is typically used for cleanup and rethrow of exception that could not be handled locally. You can use as many cath() blocks, as many you need, plus one catch_any block as the last one. Be careful with the ordering of the catch() blocks: catching a class means catching all of the parent classes as well.

3.3 Finalize the exception handling

You can notice, that there is a finally option as well, that will run in every case. It is very important that codes in the finally block can not fail (can not throw any exception)! This section will run in every case, regardless of the existence of an exception, or if it was caught or not. The finally block must be the last section of a try ... end_try block.

3.4 Closing the try block

You must close the try block sequence with an end_try; statement. You don't have to use all of the possible blocks in the try ... end_try block, however at least one catch or catch_any or finally block must be used. Every executed try must have an executed end_try! In practice this means that you must NOT return from within the try block! (Or jump out with goto, but who does use it? :-))

3.5 Protecting dynamic memory blocks and objects

Unlike C++ there is no stack unwinding during the exception handling! Consequently you must pay extra attention on memory handling in your routines: make sure that every temporarily allocated memory block is freed in case of an exception rises in the routine or in routines called. The simplest solution is a try ... finally ... end_try block. It is relatively not computationally expensive, and is effective. For example the code below will lead to memory leak if there would arise an exception:

     void my_func( void )
     {
         char * mem;
     
         mem = ooc_malloc( 1000 );
     
         do_a_risky_call(); /* If this code throws an exception then  */
                            /* the mem will never be freed, causing a */
         ooc_free( mem );   /* memory leak!                           */
     }

The correct solution is protecting the sensible variables like follows:

     void my_func( void )
     {
         char * volatile mem = NULL;
     
         try {
             mem = ooc_malloc( 1000 );
     
             do_a_risky_call();
             }
         finally {
             ooc_free( mem );
             }
         end_try;
     }

Listen to the followings in the above code:


In most cases you want to prevent memory leak only, and do not necessarily need to get the control in the case of an exception. For those situations there is a simpler mechanism described in the next section.

3.6 Managed pointers

In ooc you have an other option for preventing memory leaks in case of an exception: the managed pointers. Using managed pointers you will not get the program control in case of an exception, but it is guaranteed, that the memory is freed or the Object is deleted in case of an exception. (You may consider this as analogie for std::auto_ptr<> in C++.)
Using managed pointers is faster than using the try ... finally ... end_try constructs, so it is more advisable if you do not need the program control in case of an exception.

3.6.1 Managing a pointer

Managing a pointer means that ooc will take care of freeing the resouce in case of an exception. You can manage a pointer with the ooc_manage() macro. This macro pushes the pointer (and the corresponding destroyer function) to the top of the managed pointers stack.
If there is an exception thrown, ooc will continue the program execution at the next catch or finally statement, and takes care that all memory or Objects that are referenced by the managed pointers pushed onto the stack, are freed or deleted respectively till that point.
If there was no exception thrown, you must remove the pointer from the stack with the ooc_pass() macro.
Because the managed pointers' stack is a stack, you can remove the most recently pushed item only: you must use ooc_pass() always in the reverse order of using ooc_manage()!
Use ooc_manage() / ooc_manage_object() and ooc_pass() always as a pair in the same name scope! These macros use local variables, and the variable created by ooc_manage() must be accessible by ooc_pass()! Never let ooc_manage() be executed more than once without executing the corresponding ooc_pass() before!
To be ANSI-C compliant, the ooc_manage() and ooc_manage_object() macros always must preceed any statement in the given namespace! (This is because they define a local variable.) In practice this means that you must open a new name scope with { if you'd like to use the managed pointer in the middle of your code. Close this name scope only after passing the managed pointer. You can nest more namescopes when using multiple managed pointers. See the first example!

3.6.1.1 Manage a pointer: ooc_manage()

Pushes a pointer onto the top of the managed pointers' stack.
ooc_manage() requires two parameters: the pointer to the resource and the appropriate destroyer function for it (typically ooc_delete or ooc_free).
ooc_manage() does not return anything.

3.6.1.2 Manage an Object: ooc_manage_object()

Manages an Object like ooc_manage().
This is shortcut for ooc_manage( my_object, (ooc_destroyer) ooc_delete );.

3.6.1.3 Pass the ownership: ooc_pass()

Removes the most recently pushed pointer from the managed pointers' stack. Always use in the reverse order of using ooc_manage()!
ooc_pass() requires one parameter: the pointer to be removed.
Please note that since the most recently pushed pointer is removed, the parameter is used only for verification that the push an pop orders are correct! (In release versions this verification is skipped for gaining some speed.)
ooc_pass() returns the pointer itself.
The name is coming from passing the ownership of the pointer to an other object or function.

3.6.2 Examples

3.6.2.1 Protecting temporary memory allocation

In the previous section we used try ... finally ... end_try to prevent memory leak for a temporary memory allocation. The same with managed pointer:

     void my_func( void )
     {
         char * mem;
     
         mem = ooc_malloc( 1000 );
         {
             ooc_manage( mem, ooc_free );
     
             do_a_risky_call();
     
             ooc_free( ooc_pass( mem ) );
         }                      
     }

Simplier, faster.

3.6.2.2 Taking over the ownership of parameters
     void foo_add_bar( Foo self, Bar bar )
     {
         ooc_manage( bar, (ooc_destroyer) ooc_delete );
     
         do_a_risky_call();
     
         /* pass the ownership of bar to self  */
         self->bar = ooc_pass( bar );                      
     }
     
     Foo foo;
     
     foo = foo_new();
     
     foo_add_bar( foo, bar_new() ); /* this code is safe! */

If you we're not using managed pointers for taking over the ownership of the parameter then the parameter object would be leaked in case of an exception in the do_a_risky_call() method.