Tuesday, November 28, 2006

Exceptions (part 1)

As you already know a modern way to deal with "errors" in C++ is the exception handling; however you need to be careful on using this mechanism. Let see how it works and some tips as well.
The exception mechanism is based on the try - catch blocks:




try {
//some code in here that we attempt to execute
}
catch (...) {
// this block is the error handling, the code in this
// block is executed if the code in the
// try block above have thrown an exception
}



easy and net.

Let see how throw an exception so we can see more in depth what this mechanism offers, how use it, what to avoid.
An exception is thrown with a throw:



throw A;



where A is the type of the object thrown, in that case we are throwing an object of type A inizialized with his default constructor, we could have done:



throw A(3, "foo");



or even:



A a(3, "foo");
throw a;



The catch(...) { } handler is supposed to handle all kind of exception that code inside the try block throws, in this way we lose the kind of exception thrown so it's a bit reductive because the error handler doesn't have a clue on what is going on; fortunately is possible to specify wich kind of exceptions we want to manage (we as well ignore some).
Specify which kind of exception we want manage is done in this way:



catch(Foo f) {
}



we can have multiple catch blocks after a try:




try {
//code we are attempting to execute
}
catch(Foo f) {
// error handler in case a Foo type was thrown
}
catch(Bar b) {
// error handler in case a Bar type was thrown
}



if the code in the try block thrown an exception that is not Foo and neither Bar then is like we are not using the try-catch blocks ( apart the introduction of the try's extra scope ).

We can obtain the same behaviour (logging for example we were not able to catch any expected exception) in this way:



try {
//code we are attempting to execute
}
catch(Foo f) {
// error handler in case a Foo type was thrown
}
catch(Bar b) {
// error handler in case a Bar type was thrown
}
catch(...) {
// log the event in here
throw; // this throws again the same exception.
}



There is still something behind all this. As you have seen the catch blocks are very similar to a function declaration where the arguments are passed by value.
The catch blocks can have indeed all kind of parameters:

catch( T )
catch( T & )
catch( T * )
catch( const T )
catch( const T & )
catch( const T * )

so you can think of throwing an exception have same effect of calling a function, but is not.
Consider this code:



void foo()
{
...
A aLocalObject;
...

throw aLocalObject
}



and the call of foo is inside a try block:



try {
foo();
}
catch( A anException) {

}




in this case, as the catch "signature" suggests, the anException is a copy of aLocalObject so
no problem with it, but what if we catch by reference?



catch( A & anException ) {
}



in this case we can think to have a reference to a destroyed object ( when an exception is thrown is not like a function call and all the local variable on that scope are destroyed ), well this is not the case indeed even if you catch an exception by reference the c++ runtime support will perform a copy of the object thrown, and this happens always, you can not avoid this copy even if the object will not be destroyed going out of scope ( a static variable for example ).

So in case of:

catch( A ) { }

you have 2 copies performed, in case of:

catch ( A & )

you have just one copy. For this very reason is not possible to modify the object thrown, because you have on the catch block a copy of it.

Next week the second and last part about exceptions.

No comments: