Hello again! In this video, we are going to look at try and catch blocks. A catch look can only appear after a try block,
so we have the try block, immediately followed by a catch block.
And if an exception is thrown in the try block, then that catch block is going to be the first place
the program will look in for a handler for that exception.
We can have more than one catch block for the same try block.
And in fact, if we want to handle exceptions which can have different static types, we need to do
that.
We need to have one catch block for each static type.
If there is any choice about which catch block can handle a particular exception, it will be the first
one that appears in the source code.
So let's have a look at this. We have our vector access, inside a try block.
We have two handlers, one for the base class exception, and one for the derived class, out_of_range.
This is going to be the actual exception that is thrown by this code.
So, which catch block do you think is going to handle it?
Let's find out.
So it is actually the base class exception.
It is the first one that appears in the source code.
So even though this one is actually more specific, the exception one gets called first.
If we move the out_of_range one, so it goes above the exception handler, then that is going to be the
first one which can handle the out_of_range exception.
So we expect that one to be called and yes, there it is.
So this means if you have an inheritance hierarchy, you need to put the catch handlers upside down. So
the child classes go at the top, and then the base class goes at the bottom.
So normally we use the standard exception hierarchy. And to make this work in the way that we expect,
we put the catch blocks in reverse order.
So the most derived one comes first and the base class comes last.
When we are writing an exception handler, there are some things we should bear in mind. As we have already
discussed, the handler should take the exception object by reference, and that will allow polymorphism
to work.
So when you call virtual member functions, like what(), you will get the right version of the function for
the object that was thrown. And also, you should make that a const reference, unless you actually need to
modify the exception object.
So that is just the usual rule, really.
When the program is executing inside a catch block, this implies that something unusual has happened,
which is outside the normal control flow.
So the program may be in an unstable state.
We need to be careful that we do not disturb it any further.
So we should avoid things like allocating memory, creating new variables and calling functions.
If we do need any variables, we should try to use built-in ones if we can.
Never do anything which might throw a new exception.
That is a very bad idea.
And keep the code very short and very simple.
We are allowed to put a try-catch block inside another try block, so we can have nested
try-catch blocks.
So in here we have a try block and a catch handler, for bad_alloc.
This is another subclass of exception.
It gets thrown when there is a memory allocation error.
So, calling new().
It is actually very hard to get this on modern computers, because they do not run out of memory.
They just run very, very slowly instead.
So if we get the bad_alloc exception thrown in this block, it will be handled in this catch handler.
If we get some other exception thrown, then the program will jump out of this inner try block, into the
enclosing scope. Which is this outer try block.
And then it will look for another exception handler.
And if the exception is anything in the standard exception hierarchy, then this handler will handle
it.
So here is an example. We have our favorite vector access again, in the try block.
The catch block for this only handles bad_alloc.
So if there is some kind of memory allocation error, when the program creates this vector, we might
get this catch handler invoked.
Otherwise, it should go and try to do the access. And then the out_of_range exception will be thrown again.
There is no handler in this
try-catch block, so the program is going to jump out into the enclosing scope.
It will look for a handler here.
And because out_of_range is a subclass of exception, this handler is going to be invoked.
And there we are.
One of the advantages of exceptions is that we can handle errors in a different place from where they
occur.
Let's say, for example, we have some function that does data processing, and it calls another function
to actually get a file.
And this code in here could throw an exception.
Supposing we want to handle all the errors in this process_data function(). So how do we set that
up?
It is actually very easy.
We do nothing at all in get_file(), as far as exceptions are concerned, and we call the get_file() function
from inside a try-catch block in process_data().
So we have our process_data() function, which calls get_file() inside the try block. if any exceptions are
thrown in here.
There are no handlers.
So the program jumps out of this scope.
It returns from get_file() and it will now be inside this try block. And then it will look for the next
catch handler up, which is this one.
So it should get handled here.
And here is some slightly different code.
We have our vector access in a function, and then we have a main() function, which calls this inside
a try block.
So this is going to throw an exception, which is not handled in this function.
It is going to go back to the caller.
And then this is inside a try block, which has a handler.
So it should handle it here.
And if for some reason we get an exception, which is not a class in the inheritance hierarchy, that is
going to jump out of this scope into main(), and there is no handler there.
So we have run out of road, and the program terminates.
OK, so it was a member of the exception hierarchy, so it is caught by this catch block in main().
And there we are.
Okay, so that is it for this video.
I will see you next time, but until then, keep coding!