WEBVTT

00:00.150 --> 00:06.240
Hello again! In this video, we are going to look at error codes and exceptions. In the last video,

00:06.270 --> 00:12.120
we discussed how sometimes it is useful when you have an error condition, to process it in a different part

00:12.120 --> 00:12.600
of the code.

00:13.050 --> 00:19.050
But we need somehow to get the information from one part of the code to the other. In C,

00:19.050 --> 00:24.930
the only way to do this, or at least the only reasonably practical way to do this, is by using error codes.

00:25.860 --> 00:32.490
So when we have some function that could experience a run-time error, this will return a number and

00:32.490 --> 00:36.060
this number will be a code which represents the particular error condition.

00:37.500 --> 00:42.390
The caller of this function will check the return value, and it will decide what to do.

00:42.990 --> 00:48.330
Perhaps it could handle the condition itself, or maybe it needs to delegate it to a function higher

00:48.330 --> 00:49.800
up the call stack.

00:51.330 --> 00:56.610
If it does that, it can just return the same error code, or it could convert this to a different error code.

00:57.210 --> 01:04.200
For example, if we have some very specific network failure condition and we just want to report a more

01:04.200 --> 01:05.310
generic network problem.

01:07.600 --> 01:09.140
So we could have code like this.

01:09.160 --> 01:13.960
So here is our get_data() function, which will try to read data from a file.

01:14.710 --> 01:18.670
If it cannot find the file, then it is going to return this particular error code.

01:20.290 --> 01:26.500
Then in the code which calls get_data(), it is going to look at the return value from this function call

01:26.890 --> 01:30.370
and then it will have some codes which will handle that particular error condition.

01:31.000 --> 01:33.850
So the usual way of doing this is with a switch statement.

01:34.630 --> 01:38.860
So this means you need a case for every possible error condition in get_data().

01:42.320 --> 01:44.210
There are a few drawbacks with this approach.

01:44.600 --> 01:49.550
First of all, they make the code more complicated. Every time you have a potential error condition,

01:49.910 --> 01:55.160
you need to have an if statement to check for the error condition, a return statement to return the

01:55.160 --> 01:55.700
error code,

01:56.300 --> 01:58.730
and you might also need to clean things up.

01:59.240 --> 02:04.250
For example, if you allocate some memory, then you need to release that before you return.

02:05.840 --> 02:10.520
The caller of this function has to check the return value, and it has to be able to cope with every possible

02:10.520 --> 02:12.680
error code that this function could return.

02:13.220 --> 02:15.980
So there is quite a lot of coupling between these two functions.

02:18.980 --> 02:23.000
And in fact, error codes generally can create programs which are difficult to maintain.

02:23.660 --> 02:25.640
You can end up with a large switch statement.

02:26.030 --> 02:30.950
There is one program I worked on, which was basically a main() program, which had a function call and

02:30.950 --> 02:31.940
then a huge switch

02:31.940 --> 02:36.680
statement on the return value from this function. And the switch statement was about 500 lines

02:36.680 --> 02:37.010
long!

02:37.370 --> 02:39.080
And there are 10 or 20 people working on it.

02:39.740 --> 02:41.240
And it just kept breaking all the time.

02:41.240 --> 02:42.620
It was just too big to manage.

02:44.700 --> 02:50.250
So this switch statement has to be synchronized. Every case label in the switch statement must match

02:50.670 --> 02:55.440
an error condition in the function. And if you change them and you forget to update it, then you are

02:55.440 --> 02:56.280
going to have problems.

02:57.290 --> 03:02.310
If you add a new error code, or you discover a new error condition, then you need to change this

03:02.310 --> 03:05.090
switch table, and you probably need to change other parts of the code as well.

03:06.240 --> 03:11.670
Error codes do not work very well if you have callback functions. If you pass afunction as an argument

03:11.670 --> 03:17.490
to a function call, and then you need to get the error code from that, that is a bit more complicated.

03:18.660 --> 03:24.090
And finally, with C++ constructors, you are completely stuck because constructors do not

03:24.090 --> 03:27.120
return anything. So you cannot use of error codes with constructors.

03:31.700 --> 03:37.370
In C++, we have exceptions which we can use instead of error codes. If we have some code which could

03:37.460 --> 03:39.140
experience a run-time error,

03:39.680 --> 03:43.190
we put that inside its own scope, so a pair of curly braces.

03:44.500 --> 03:51.220
If an error occurs, we create an exception object and we exit the current scope. So we jump out of

03:51.220 --> 03:51.760
the braces.

03:52.960 --> 03:55.600
We find some code which can handle this exception.

03:56.020 --> 03:57.760
This exception will have a particular type.

03:57.760 --> 04:01.600
It is a class object. So the handler will depend on the type of this object.

04:02.530 --> 04:07.630
And then we get the program to jump into this handler code and process the exception object.

04:10.890 --> 04:16.560
As programmers, we need to say what type this exception objects will have, and we need to provide code

04:17.070 --> 04:18.060
to process it.

04:18.960 --> 04:24.900
We also need to say when we want to create the exception objects. And the compiler will do the rest

04:24.900 --> 04:25.320
for us.

04:25.580 --> 04:30.720
It will generate codes at run-time, which will create this exception object and make sure that the program

04:31.020 --> 04:32.400
jumps into the correct handler.

04:35.660 --> 04:41.660
So the advantages of this are we no longer have to write lots of tedious and error-prone coding, to look at error codes

04:41.660 --> 04:48.050
and match them up and make sure the correct handler gets called. The compiler and the run-time will take

04:48.050 --> 04:53.030
care of the control flow. Because we have typed objects and not just numbers,

04:53.660 --> 04:57.740
it will make a difference if we return the wrong object or try to use the wrong handler.

04:58.990 --> 05:01.750
And we do not have to maintain a switch statements and error codes.

05:03.580 --> 05:08.860
If we do not provide any code to handle the exception, the program will terminate immediately, instead

05:08.860 --> 05:13.930
of running on and getting into an inconsistent state. An exception is an object of a class, so

05:13.930 --> 05:20.320
it can convey more information than just a number, and it works well with callbacks and constructors.

05:22.660 --> 05:28.340
There are disadvantages. There is extra code, which needs to be executed at run-time. Throwing an exception

05:28.360 --> 05:31.060
takes much longer than returning an error code.

05:31.750 --> 05:37.510
However, if we have a modern implementation on modern hardware, there is no penalty in the case where

05:37.510 --> 05:39.210
we follow the normal flow.

05:39.220 --> 05:40.480
So without an exception.

05:41.790 --> 05:46.470
There are some environments where the overhead is not acceptable: in real-time systems, where there

05:46.470 --> 05:49.650
are fixed time limits on how long operations can take.

05:50.240 --> 05:52.860
In embedded systems which have limited resources.

05:53.970 --> 05:57.480
If you are writing a game, you may run into either one or both of these situations.

05:57.810 --> 06:00.780
You may be tight on time, or tight on resources.

06:04.120 --> 06:08.950
If you work for a company, they may have a coding standard which says you cannot use exceptions.

06:09.310 --> 06:11.740
Google was famous for doing this in the 2000s.

06:12.370 --> 06:17.050
"Thou shalt not use exceptions in C++!". But they have actually changed that since then.

06:17.950 --> 06:22.000
The reason for this is that it took compilers a long time to get exceptions right?

06:22.570 --> 06:27.940
There were some early implementations which were buggy and inefficient, and some people have bad memories.

06:28.150 --> 06:34.030
But they do work well now. And there is some extra code we have to add to provide the "plumbing",

06:34.030 --> 06:40.240
if you like, for the exception mechanism. But we can minimize that if we use the RAII idiom. And I

06:40.240 --> 06:41.800
will show you how to do that, later on.

06:42.700 --> 06:45.520
Okay, so that is it for this video. I will see you next time.

06:45.700 --> 06:47.620
Until then, keep coding!
