Next: Using Classes, Previous: Objects and Classes, Up: Top [Contents][Index]
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:
finally
option.
end_try
statement.
try
must have an executed end_try
!
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!
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.
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 subclasses. 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 subclasses as well.
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.
try
blockYou 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? :-))
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:
volatile
storage class specifier for those variables that change their value in the try
code section, and you would like to use this new value in any of the catch
or finally
blocks, or after the end_try
statement!
This is necessary, because the try
solution is based on setjmp/longjmp
, that may change the register values, so we must prevent optimizing compilers using registers for these variables. Forgetting setting volatile
typically brings you in a situation where the debugged code works properly, but the optimized release fails while handling exceptions.
NULL
!
This is necessary because the local variable is located on the stack, and gets a random starting value. If you forget the initialization, and the memory allocation would fail, then freeing this pointer in the finally
section would refer to an undefined memory block, and most probably would cause a segmentation fault.
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.
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.
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!
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.
ooc_manage_object()
Manages an Object like ooc_manage()
.
This is shortcut for ooc_manage( my_object, (ooc_destroyer) ooc_delete );
.
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.
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.
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.
Next: Using Classes, Previous: Objects and Classes, Up: Top [Contents][Index]