Exceptions are an error handling mechanism built into C++. They are handled through the mechanism of try and catch blocks.
When the error occurs, the code immediately exits the try block and runs the catch block. This means that allocated memory may not be freed. Consider the following code:
#include <iostream>
int main() {
try {
int *number = new int {};
if (*number == 0) {
throw 0;
}
delete number;
} catch (int e) {
std::cout << "Exception thrown. Memory leaked." << std::endl;
}
return 0;
}
This is another reason to use smart pointers. They are exception safe. If you throw a object, it must be copyable. This means a copy constructor must be available, either default or custom.
If an exception is thrown but not caught, we get a hard crash. We don’t have to directly catch exceptions, for example, an exception can be thrown in a function but caught in the calling code. It doesn’t have to be the direct calling code.
We can nest try blocks. The exception will handled by the closest matching catch,
When throwing types catch them by reference. If passing by value, then two copies will be made, the one when transferring control to the catch handler and the one that is made to pass it into the handler. Passing by reference avoids an unnecessary copy. The order of catch blocks is important. If we catch a base class first, all the sub-classes are also handled. As in Java, we can re-throw exceptions or you can re-throw a different exception. The syntax is simply throw; or throw exception-type;
In C++, we can control what happens when an unhandled exception terminates our program. By default, the terminate function in the exception header is called and this in turn calls the standard library abort function. We can simply write our own handler function and set it as the terminator.
#include <exception>
void terminate_function() {
// Handle any necessary tasks and logging
std::abort();
}
std::set_terminate(&terminate_function);
If we don’t call abort(), the system will terminate our program anyway, but it is considered good practice to call it.
We can catch all remaining exceptions by using the … specifier. This must be last catch block in our chain.
try {
some_func();
} catch (... e) {
// Handle the exception
}
The noexcept specifier indicates that no exception may propagate beyond the bounds of the function. We can still throw and catch exceptions within the function.
void some_func() noexcept {
Destructors have an implicit noexcept so if we try to propagate an exception out of a destructor, our program will crash. It is possible to propagate exceptions out a destructor by passing false to noexcept.
There are a group of standard exceptions which should be used in preference to creating our own. When we do create own own, we should extend one of these standard exceptions.