C++: Try and Catch
When programs become more complex, it is often the case that different errors and things that would create problems should be handled (for example runtime errors). The subject of error handling, or exception handling, is very large and complicated, and often solutions can be accomplished in a number of ways. Some people swear by simply having if-statements that stop execution in cases that could be problematic, and some people like having a custom function that can deal with errors by being passed some kind of number or code. C++ provides a pretty neat way to handle exceptional circumstances natively though, and this functionality is provided through three main keywords: try
, throw
, and catch
.
The general idea is that code which has the potential to go wrong in some way is wrapped in a try
block, using the throw
keyword when it encounters an exception, and then one of the catch
blocks which follows the try
block can handle the exception in some way. A nice example to demonstrate the use of this might be a simple application which divides two numbers, like the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
The above would work perfectly fine (not to say that it couldn't be improved by passing-by-reference and various other things), however what would it do if the user entered 'b' as '0', since dividing by 0 is "impossible"? The answer is that it would probably give some kind of runtime error or exception. In a "real world" application, the user probably wouldn't know what this meant and it'd be awfully confusing for everybody involved in the process of trying to help fix the problem. A nice way to deal with this might be to use try
, throw
, and catch
! We could surround the 'return' code in the function in a try
block, use an if-statement to throw
an exception if 'b' is 0, and then catch
the exception and cout
some kind of error. So conceptually, something like the following:
1 2 3 4 5 6 7 8 |
|
The throw
statement behaves much like return
. You can (and usually do) pass it a value, and then this value is passed as a parameter into the catch
block. Often either int
or string-esque data-types are thrown as they can either represent an error code or text explaining the error, however it isn't uncommon for some custom class to be thrown either. In this example, we're just going to throw an integer error code and then output the error:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
We also ought to return
something, as currently nothing is returned if division by 0 is attempted. Since execution usually continues like nothing happens after "try-catch" sections, we can simply place some kind of error return after all of this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
This is not, however, an ideal solution. Firstly, we're having to take up memory copying the error code as a parameter into the catch
block - we could solve this by making the block take a reference: catch(int& err_code)
, and secondly we're still outputting '-1' to the user, even if we know these results are not accurate.
To resolve the second of these issues, we can make use of the fact that throw
works its way back to find the "closest" try-catch block that it can. In this case, it would be perfectly fine (and in many ways better) for our function to do the throw
ing , however to avoid putting the try-catch block in there. If we put the function call inside a try-catch block, the throw
would still trigger any matching catch
blocks! Take, for example, the following, which prevents output if an integer exception is thrown:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
In a case like the above, we may not, however, know that an exception with an integer will be thrown. If an exception with an integer is thrown, then brilliant, output that integer as an error code, but if any other kind of exception is thrown, we currently aren't dealing with it. A default catch
can be made by giving an ellipsis as a parameter - this should be at the bottom of the chain if there are multiple catch
s as it will handle any and all exceptions. An example implementation is as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
There are some cases in which it's especially good practice to use a try-catch block. Dynamically allocating memory is a prime example of this -- if we allocate memory using the new
keyword and not enough memory is available, an exception of type "bad_alloc" will be thrown. This inherits from the base "exception" class which can be found in <exception>
, which is a standard class for exceptions. Provoking the what
member function on an "exception" object should tell us what its problem was, and this can be seen by trying to allocate a large amount of data - 100000000000000 was enough for my machine:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
If you're interested in using standard "exception" objects for your own exception handling using try-catch (which isn't a bad idea), the class is part of the 'std' namespace, and much more information about it can be found from various online resources.