Hello again! In this video, we are going to look at error codes and exceptions. In the last video,
we discussed how sometimes it is useful when you have an error condition, to process it in a different part
of the code.
But we need somehow to get the information from one part of the code to the other. In C,
the only way to do this, or at least the only reasonably practical way to do this, is by using error codes.
So when we have some function that could experience a run-time error, this will return a number and
this number will be a code which represents the particular error condition.
The caller of this function will check the return value, and it will decide what to do.
Perhaps it could handle the condition itself, or maybe it needs to delegate it to a function higher
up the call stack.
If it does that, it can just return the same error code, or it could convert this to a different error code.
For example, if we have some very specific network failure condition and we just want to report a more
generic network problem.
So we could have code like this.
So here is our get_data() function, which will try to read data from a file.
If it cannot find the file, then it is going to return this particular error code.
Then in the code which calls get_data(), it is going to look at the return value from this function call
and then it will have some codes which will handle that particular error condition.
So the usual way of doing this is with a switch statement.
So this means you need a case for every possible error condition in get_data().
There are a few drawbacks with this approach.
First of all, they make the code more complicated. Every time you have a potential error condition,
you need to have an if statement to check for the error condition, a return statement to return the
error code,
and you might also need to clean things up.
For example, if you allocate some memory, then you need to release that before you return.
The caller of this function has to check the return value, and it has to be able to cope with every possible
error code that this function could return.
So there is quite a lot of coupling between these two functions.
And in fact, error codes generally can create programs which are difficult to maintain.
You can end up with a large switch statement.
There is one program I worked on, which was basically a main() program, which had a function call and
then a huge switch
statement on the return value from this function. And the switch statement was about 500 lines
long!
And there are 10 or 20 people working on it.
And it just kept breaking all the time.
It was just too big to manage.
So this switch statement has to be synchronized. Every case label in the switch statement must match
an error condition in the function. And if you change them and you forget to update it, then you are
going to have problems.
If you add a new error code, or you discover a new error condition, then you need to change this
switch table, and you probably need to change other parts of the code as well.
Error codes do not work very well if you have callback functions. If you pass afunction as an argument
to a function call, and then you need to get the error code from that, that is a bit more complicated.
And finally, with C++ constructors, you are completely stuck because constructors do not
return anything. So you cannot use of error codes with constructors.
In C++, we have exceptions which we can use instead of error codes. If we have some code which could
experience a run-time error,
we put that inside its own scope, so a pair of curly braces.
If an error occurs, we create an exception object and we exit the current scope. So we jump out of
the braces.
We find some code which can handle this exception.
This exception will have a particular type.
It is a class object. So the handler will depend on the type of this object.
And then we get the program to jump into this handler code and process the exception object.
As programmers, we need to say what type this exception objects will have, and we need to provide code
to process it.
We also need to say when we want to create the exception objects. And the compiler will do the rest
for us.
It will generate codes at run-time, which will create this exception object and make sure that the program
jumps into the correct handler.
So the advantages of this are we no longer have to write lots of tedious and error-prone coding, to look at error codes
and match them up and make sure the correct handler gets called. The compiler and the run-time will take
care of the control flow. Because we have typed objects and not just numbers,
it will make a difference if we return the wrong object or try to use the wrong handler.
And we do not have to maintain a switch statements and error codes.
If we do not provide any code to handle the exception, the program will terminate immediately, instead
of running on and getting into an inconsistent state. An exception is an object of a class, so
it can convey more information than just a number, and it works well with callbacks and constructors.
There are disadvantages. There is extra code, which needs to be executed at run-time. Throwing an exception
takes much longer than returning an error code.
However, if we have a modern implementation on modern hardware, there is no penalty in the case where
we follow the normal flow.
So without an exception.
There are some environments where the overhead is not acceptable: in real-time systems, where there
are fixed time limits on how long operations can take.
In embedded systems which have limited resources.
If you are writing a game, you may run into either one or both of these situations.
You may be tight on time, or tight on resources.
If you work for a company, they may have a coding standard which says you cannot use exceptions.
Google was famous for doing this in the 2000s.
"Thou shalt not use exceptions in C++!". But they have actually changed that since then.
The reason for this is that it took compilers a long time to get exceptions right?
There were some early implementations which were buggy and inefficient, and some people have bad memories.
But they do work well now. And there is some extra code we have to add to provide the "plumbing",
if you like, for the exception mechanism. But we can minimize that if we use the RAII idiom. And I
will show you how to do that, later on.
Okay, so that is it for this video. I will see you next time.
Until then, keep coding!