In this video, we'll learn about some of the concepts,
terminology and syntax of exception handling in c++,
and then we'll see a simple example that uses exception handling
to deal with the divide by 0 condition.
Let's start with what exception handling is all about.
First, exceptions in c++ should be used
only for synchronous code, not asynchronous code.
Exception handling is about dealing with extraordinary situations.
How we define what an extraordinary situation is,
is really dependent on the application and on the designer.
We want to be able to detect
when an extraordinary situation has occurred or is about to occur so that we can deal with it.
Again, what does deal with it mean? It depends on the application.
In some cases, we'll be able to recover and continue. But in other cases,
maybe the only alternative is to terminate the program.
But in this case, we can control how we terminate the program.
Perhaps, we can close files, save as much data as we can and fail soft.
So what can cause these exceptional or extraordinary situations?
One cause could be insufficient resources.
Maybe we're out of memory or we have no external storage left, and we need that to continue.
Another cause is missing resources.
Suppose we need to open and read a file, but that file doesn't exist.
If this file is critical to our program,
then we have to decide how we're going to continue if it's even possible.
Invalid operations, range violations, underflows and overflows,
there are many many causes for exceptions.
Our programs are said to be exception safe when our code handles exceptions.
This is very difficult to do completely in c++.
Now let's learn about the terminology used in c++ when we talk about exception handling.
This terminology is fairly consistent with many other popular programming languages
that you might be familiar with.
However, the way it works with c++ tends to be different.
First, an exception is an object or primitive type such as an int,
a double, a Boolean and so forth. And it signals that an error condition has occurred.
In many cases, this exception object also contains information
about what happened.
When your code determines that something is wrong,
then it can throw an exception object.
Why can't the code just deal with the problem?
Well, in some cases it can, and it doesn't throw an exception, it just handles it itself.
However, the more common use case is that the problem is detected
in one place in your program, but that part of the program
doesn't know what to do about it.
So it throws an exception in hopes that some other part of the program can deal with it.
That's where catching an exception comes in.
Some other part of the program could contain the code
that catches the thrown exception and handles it.
In some cases,
dealing with this simply means displaying or logging an error message and terminating the program
that's okay if the program can't continue, and we fail soft.
In other cases, we may be able to handle the exception
recover and continue processing.
For example,
suppose I want to allocate some memory dynamically, but there isn't enough memory available.
The part of our code that allocates the memory
would throw an exception since it couldn't allocate any more memory,
but it doesn't know what to do.
Then in some other part of our program, we can catch this exception,
and maybe we can clear out some buffers or caches
and then release some memory and then try to allocate it again.
Now let's see the keywords that c++ uses to achieve exception handling.
In c++, we use three keywords to work with exception handling.
These are throw, try and catch.
The throw keyword is used to throw an exception object or primitive type,
and it's usually followed by an argument, which is the exception that we're throwing.
The try keyword is followed by a code block in curly braces.
The code in the code block is code that could potentially throw an exception.
So we place that code in the try block.
If the code does not throw an exception, then the code executes as normal.
However, if the code in the try block throws an exception,
then the rest of the code in the block does not execute,
and c++ looks for a block of code that can handle that thrown exception.
That's the catch block, which is the catch keyword
followed by the type of exception object that it handles.
It also has a code block where the code that handles the exception executes.
Catch blocks only execute if an exception is thrown,
and the type of the thrown exception matches the parameter in the catch block.
We can write multiple catch handlers that expect different types of exceptions.
The best way to understand exception handling is to really just look at some examples.
Let's start with a very simple example that divides by 0.
Here we have a code snippet that divides sum by total
and stores the resulting average.
Let's assume that sum and total have been declared and initialized.
So what happens when total is 0.
Does the program crash, do we get an undefined result?
The answer is yes to all of them. It really depends
because the result of a division by 0 depends on the types of the arguments
in the division operation.
Integer divided by 0
and floating point divided by 0 may provide different results. We'll see this in a minute in the IDE.
Regardless, we can't let that statement execute. So what do we do?
Well, that's pretty easy. We check to see if total is 0.
And if it is, we don't do the division.
And if it is not equal to 0, we go ahead with the division.
That's pretty easy. The harder question is what do we do when total is equal to 0?
Well, that totally depends on the application.
And this question becomes much more difficult
if this code existed in a function
and that function calculates and returns the average.
What do we return from that function if total is 0?
So we can use exception handling to deal with the situation.
This is an example to show you the syntax for exception handling.
You wouldn't write code like this since we could print an error message easily
with a simple if else statement as in the previous slide, but let's look at this code together.
Again, let's assume that sum, total and average have been declared and initialized.
First, we have a try block.
Inside the code in the try block,
we write code that could potentially throw an exception.
In this case, if total equals 0, we throw an exception.
The type of the exception in this example is an integer, and we set it to 0.
Best practice is to throw objects, not primitives,
but we'll get to throwing objects soon.
Let's first look at the case where total is not equal to 0.
In this case, the try block executes, the division occurs
and we can use the average, and the try block is normal.
When the code in the try block completes, we transfer control to the last statement
that displays program continues.
Notice that the catch block does not fire since no exception was thrown.
Okay. Now let's suppose that total is equal to 0.
Now the code in the try block throws 0.
No further code in that try block is executed.
So the divide by 0 does not happen.
Now we transfer control to a catch handler
that expects an exception object of the same type as the one we threw,
in this case, an integer. The code in the catch block now executes.
And when it's done, we transfer control to the last statement that displays
program continues again.
Notice that we prevented the divide by 0 error,
and we handled it in a controlled manner.
Now let's head over to the IDE, and we'll see this example in action.
Okay. So I'm back in the CodeLite IDE.
I'm in the section 18 workspace, and I'm in the MPG project.
MPG stands for miles per gallon, and that's what we're going to calculate.
Apologies to my kilometers per liter friends out there. So what do we do.
We've got miles, we've got gallons, we're going to ask the user for those two values. They're both integers,
and then we're going to calculate miles per gallon, which is a double.
Pretty straightforward stuff that we've done already.
So in this case, notice on line 15, here I'm calculating
miles per gallon equals miles divided by gallons.
Now there's a problem here, we're going to get some truncation because you can see that miles
right here and gallons are both integers.
So this is going to be integer division, and we're going to truncate the
the decimal part, right.
But the reason I'm doing it this way is to show you the error that happens
when you do integer division by 0.
Okay. So but before we do that, let's run this with just some normal data.
So let's say we have 13 miles and
3 gallons.
Our result is 4. You can see it's not 4 point something because we're doing integer division.
But that's okay. We can fix that in a minute.
Now let's do a division by 0 and see what happens.
So I'm going to run this again.
And let's say I put in 13. And this time I put in 0 for gallons.
Now when I press enter, it's going to try to divide 13 by 0,
and it's going to crash. So I pressed enter.
There's the crash on windows. Okay. I'll cancel that,
close the program, and I'll terminate this program.
We don't want to crash. Okay. Now if I comment this out,
and we do a division by 0 with a floating point number.
And what we're doing here is what we did a while back in the course where we had a static cast.
Remember, these are both integers right here,
miles and gallons. So I really don't want to do integer division.
So I just need to make one of them a double, and then double division will occur.
So I'm statically casting miles to a double. I could have statically casted gallons or both of them.
So in this case, we're doing double division.
And the behavior is different here. And I'll show you why.
Let me run this again.
Okay. So let's -- again, let's do something valid. Let's say 130
divided by 3 gallons,
result is 43.3 miles per gallon. That's fine.
Now let's do it with a 0.
So I'll get 123 this time,
0 gallons. So we're going to get a divide by 0 here. We're not going to crash though.
You can see that the result that prints is infinity.
And that's the standard in c++
when you do floating point division by 0.
If i run this again
and I enter negative for miles and obviously, we would have to do checks for all of this stuff and 0,
then I'll get a negative infinity. You can see that result right here.
And the last thing if we type 0 0,
so we do 0 divided by 0, we get not a number.
And that's the error that you see here.
All those are bad outcomes. We really don't want those.
So the easy fix is not to execute line 16
if gallons is equal to 0, right.
It's really that simple. So let's do that. Let's do an if statement. If
gallons is not equal to 0,
then we do want to execute this code, right. We're safe.
And we'll use an else statement here.
Let me see CodeLite puts an extra curly in there for me. So else --
we've got an error so we want to display the error.
And what we'll do is we haven't used standard error or cerr yet.
It's another output stream that's pretty much reserved for error messages or error logging.
And I'll talk about that a little bit more when we talk about file error.
By default cerr is your console, just like cout.
So we should see this on the screen.
And we'll do an endline here.
Okay. So far so good. Like -- again, nothing we haven't done before.
But in this case, we're just handling this error.
And we're not using exception handling here, we're just using a
regular old logic to handle the error. So let me run this.
And let's say 130 miles by 0, now we should have, sorry can't divide by 0,
right, just like what we'd expect.
Okay so now let's rewrite this program so that we use exception handling.
And what we'll do is we'll load the MPG exception project right here,
and I'll load that and I'll be right back.
Okay. So I loaded the MPG exception project
in the section 18 workspace. And you can see that right now it has no exception handling,
and it has no if else logic either.
Now here's the commented code that does the integer division. I'm just going to get rid of that
because I really don't want to clutter up. And I'm still doing our floating point division here.
So what do we do to get exception handling working here.
Well, we have to have a try block. So we're going to say right here try
to execute this code. And I'm going to wrap this code up right here
inside the try,
and I'll indent this.
Okay. So what we do here is we try to execute code that could potentially throw an exception.
And when do we throw the exception? Well, we throw the exception if gallons is equal to 0.
So let's do that right in here. I'm going to say if gallons
is equal to 0,
that's where I want to throw.
And what do I throw?
An exception could be anything, it could be an integer, a character, a pointer, an object.
Right here we're just going to throw an integer, later on we'll use objects.
Best practice is to use objects, but let's just use an integer here so it's easy to understand.
So what do I want to throw? I could throw any integer, I could throw 0,
right, I could throw gallons, I could do anything I want, but let's just throw 0.
Now what happens. If gallons is equal to 0,
then we're throwing this 0.
Okay. If gallons is not equal to 0, then our code executes as normal.
But what if we do throw this 0 here,
if we do throw this exception right here, this piece of code here is not executed at all.
Okay. It's totally skipped. And then what happens is that
the c++ system is looking for that exception
that was just thrown. If you don't catch it,
then the program will terminate.
But if you catch it, then you can deal with it. So how do you catch it?
Well, you catch it using a catch block, and you can put that right here. I'll say catch.
And what's the type of the exception we want to catch? In this case, it's an integer.
And you always want to catch by reference. That's the best practice.
Name this thing any thing you want. We really aren't going to use it.
And now whatever code we want to execute in here
is what's going to happen when the error is thrown, when the exception is thrown.
And I'm just going to paste that same cerr message that I had earlier.
Okay. So now, again, let me walk through this with you really carefully because
in the next few videos it'll get a little bit more complicated. So
here's my try block. You can see it between lines 15 and 20.
And here's my catch handler right here or my catch block.
I tend to use both names synonymously.
So again, if the user entered 0, we throw it,
right. This code is not executed thankfully. Otherwise, we'd have a divide by 0 right here.
We try to catch an integer.
If there is a catch block but it doesn't catch an integer or even if there's no catch block,
then we've got -- our program will terminate right on the spot.
In this case, it will -- because we're right in our main.
Then if we do throw 0 and we do have a catch handler for it,
we'll catch it right here.
Now i could print out ex if I wanted to, right, it will print out 0 because that's what was thrown here,
that's what this guy is right here, that's the object that was thrown,
the exception.
So in this case, there's my catch handler.
In either case, my program will not crash because --
let me walk, again, right through clear this and this.
If I do throw an exception, it's handled here.
If I don't throw an exception, it's handled here.
In both cases, this line will execute.
My program will continue normally, it won't crash.
All right. So let's give this a run.
Let's say 130, and we'll do 0 again.
Sorry, you can't divide by 0. So our exception is being handled.
Okay. Now you might ask, `so what? same thing I could have done with an if else statement.
Yes. And this is not typically the way that we do exception handling.
This is just showing you the syntax and what's going on.
Usually, we do the exception handling when a function throws an exception,
and that function has no idea what to do with it
and we catch that exception somewhere else.
And that's where we're going to go into the next few videos. Okay. I'll see you there.