Hello again! In this video, we are going to start looking at the standard exception hierarchy.
C++ has a standard exception class in the library, and this is the base of an inheritance hierarchy.
So if we use this hierarchy for our exceptions, we will have a consistent interface for all exception
objects.
And also, we can take advantage of polymorphism. So we can just write one handler, which takes the
exception base class by reference.
We only need to write separate handlers for subclasses if we want to do something different for a specific
subclass.
The hierarchy looks like this. We have the exception as the base.
Then we have these classes, which we are going to look at in the next video. We can recognize bad_alloc,
which is for memory allocation errors.
And there is also bad_cast, which we will cover when we look at run-time type information.
So these are leaf classes of the exception.
There are also two sub-hierarchies, runtime_error and so-called logic_error.
And we can see that out_of_range is a logic_error.
The idea of these classifications is that a logic error is something that the programmer could have
prevented, whereas a runtime error is something that the programmer cannot do anything about.
Typically, logic errors involve preconditions.
For example, if a function must never be called with argument 0, then the programmer can check
for that before calling the function.
On the other hand, if some program on the computer goes crazy and uses up all the memory, then there is
nothing you can do about that.
The exception base class defines five public member functions.
So that is the interface of the hierarchy.
We have the usual special member functions: constructor, copy constructor, assignment operator.
There is a virtual member function called what(), and that means that the destructor has to be virtual
as well.
And I hope you remember why that is: it is to make sure that all the destructors are called when a child
is destroyed through a reference to the base.
This what() function will return a C string, and it cannot throw exceptions.
And we certainly do not want it to!
The exception class and its leaf children, bad_alloc and bad_cast, have default constructors.
The others do not have a default constructor.
Instead, they have a constructor which takes a single string argument, and that can be either a C string
or a C++ library string.
When the what() member function is called, this string is returned, so it is important that this constructor
arguments gives information about the error condition.
We saw that we can do bounds checking with vectors, by calling the at() member function, and we can actually
implement that ourselves.
Obviously, we cannot change the library vector, but we can write a non-member function which takes a vector
and does the same thing.
So this needs to take the vector and also the index.
The return type will be the same as the element type.
I am just using a vector of int here, but
you can be generic and write a template function if you like.
We need to check that the index is valid.
If the index is not valid, then we need to throw the out_of_range exception.
So we create an out_of_range object by calling the constructor, and we pass a string as argument.
So we have a local variable, which contains some information about the error condition, and we pass that
as argument. And then we throw the exception.
So the program will call the destructor for this local string variable.
Then it will jump out of the if block.
It will look for a handler in the enclosing scope.
There is nothing in this at() function, so it is going to go to the caller.
In main(), we are calling this inside a try block, which has a handler for any child of exception.
So this exception will be caught and handled in main(). And that is the exceptional program flow.
If the index is valid, then we can just carry on and use the subscript to get the element value
and return it.
So let's try this. We have a vector with three elements, so we should be able to access the element
with index 2 and get the value of that element, which is 3.
And there we are.
If we try this with an empty vector, then what should happen?
This condition is going to be false, and we are going to throw an exception, which should be caused by
this handler in main().
So let's see if that works.
And yes, it does. So exception is caught in main().
And that is the what() string.
So when we constructed the object, the argument that we gave is the message that we get when we call
what().
And finally, C++11 added a few new classes to the hierarchy.
So bad_alloc now has a child, which is bad_array_new_length.
So if you are calling new to allocate an array and the number of elements is invalid, then this will
be thrown.
There are a few new exceptions for C++11 features we have not yet covered: weak pointer, standard
function and standard future.
And I am sorry, but this does not involve time travel.
It is about multi-threading.
There's also a new sub-hierarchy, system_error, underneath runtime_error, and this has one child, ios_base_failure.
So we can now get streams to throw exceptions.
Okay, so that is it for this video.
I will see you next time.
Until then, keep coding!