Errors will happen. The question is how will you deal with them? The QA Friday from 2015 Dec-11 talked about this question. This episode explains C++ exceptions. C# also has exceptions. But C# is different enough to need its own episode.
A good way to summarize exceptions is that they allow you to detect and error condition in one place in your code and make sure that the error will eventually get handled in some other place in your code. This allows you to focus on writing your code without stopping to check for errors after every method call.
It also allows you to automatically clean up resources such as memory that needs to be freed or files that need to be closed. The technique for this relies on something called RAII which stands for “resource allocation is initialization.” Whenever you allocate some memory for example, instead of storing a pointer to that memory in a raw pointer, use a smart pointer such as std::shared_ptr which will take ownership of the pointer and make sure it gets deleted at the proper time when it’s no longer being used.
Exceptions give your program an efficient and consistent way to handle errors even in cases where the alternatives are either unreliable or difficult to enforce. You can’t use exception everywhere though. If you call a web service or a method in a COM component or some other remote service that’s outside of your application process, then you’ll have to use error codes or some other mechanism to inform the calling code that an error happened.
Listen to the full episode for more or you can also read the full transcript below.
Transcript
Exceptions are a really good way to deal with three types of errors:
◦ #1 are programmer errors. These have nothing to do with the user and could have been avoided with more robust code.
◦ #2 are user errors. These are caused by some action or input from the user. They could be accidental or on purpose.
◦ And #3 are resource and environment errors. These could be caused indirectly by the user or might just as likely be due to conditions beyond the user’s ability to control and beyond the ability of the application to detect ahead of time.
The reason exceptions are a good choice for dealing with these errors is because they can happen at any time and without exceptions, you’ll need to deal with all of them for almost anything your program does. Now, you need to deal with exceptions too, but they make your code more robust and readable. Exceptions are not just for “exceptional” or “disaster”scenarios. They really just allow one part of your program to detect an error and then let some other part of your program handle that error.
For example, let’s say that you need to allocate several different blocks of memory. Without exceptions, you would need to check each allocation request to make sure that the memory was available. You end up repeating a lot of error handling code each time you need to allocate memory. And this is just allocating memory. Need to open several files from the hard drive? You have to check each one to make sure it could be opened. And then when you want to read or write information from the file, you need to check the outcome of each of those actions as well for an error code. Maybe the hard drive just ran out of disk space after successfully writing several pieces of data. You never know when a failure will happen so you need to check for error codes again and again.
This tends to turn your code into more error handling than actual code. And what do you do for constructors that have no return value? How can you return an error code when there’s no way to return anything?
With exceptions however, you can write your code to focus on the happy scenarios and keep all the error handling separate. But there’s another benefit, one you can’t ignore.
Let’s say that you call a method named find that returns some information you want to use. And this method has several possible errors. The method can detect most of its errors but may not be able to handle all of them. What does it do for the errors that it has no idea how to handle? It could return an error code. If it does this, then you have to check for an error and then handle that error. But what if you forget to check for an error? Maybe when your code was first written, the find method returned just one error code. Later, the method was modified to return codes for different errors. Just because a method returns a value doesn’t mean that you have to do anything with that return value. You can ignore the return value. Or maybe the find method calls other methods that can also encounter errors. You end up with a chain of error codes that rely on each stage handling the errors with known solutions and passing the unknown errors up the chain. This requires code to do all this work. Code that you have to write. Code that’s easy for any of us to get wrong.
Well, you can’t ignore an exception. When an exception is thrown, control immediately starts leaving the current scope and searching for a handler that knows how to deal with the exception. All the local objects are cleaned up. The code doesn’t return to the calling method as a simple return. Whenever an exception is thrown, it starts looking for code that can catch it. It’s possible that the original method that threw the exception could catch the exception itself. If not, then control passes to the calling method. If the calling code doesn’t have a catch block, then the same cleanup happens there and control passes to the next method. It keeps doing this until it’s caught. And if the exception is never caught, then your program will usually end.
You could use exceptions for normal operations too but I don’t recommend that. It’s just unexpected and likely to confuse other developers. What I mean by this is let’s take that find method just described. When it finds the information it was looking for, it could if you wanted to write it like this, throw the result. The calling code would then have to catch the result. This looks like an error but it could just be the normal practice. It’s just too unexpected to be worth any potential gains.
Let’s take a closer look at exceptions. What are they and what exactly happens right after this message from our sponsor.
( Message from Sponsor )
First of all, anything that can be copied can be thrown as an exception although it’s common to throw instances of classes designed specifically for this purpose. You have several classes defined already for you in the standard library. Some common types are:
◦ exception – yes, the class is just called exception and forms a base class for many other standard exception classes. This class defined a method called what that allows you to retrieve a descriptive string that describes the exception. One thing to note is that exception messages are not meant for a user. These are not error messages that should be displayed to your users. You can write the messages to a log file but don’t display them. You can choose to handle an exception and display your own message. That’s fine and gives you a chance to make the message more applicable to the user and also translate the message to the user’s language. Exception messages are not translated.
◦ logic_error and runtime_error are two classes that derive from exception and each serve as base classes for other more specific exception types. The logic_error and its derived types are usually due to poor programming while the runtime_error and its derived types usually result unavoidable problems encountered while the application is running. One thing to note is that all exceptions happen while a program is running. The logic_error exceptions just normally need some additional code to prevent them.
When your code detects a problem, it should go ahead and solve or work around that problem if possible. This could also involve retrying an operation. But if the code can’t solve the problem, then it should throw a class derived from the standard exception class. This might even be one of your own exception classes. Or it could be one of the standard library exception classes.
throw is a C++ keyword and takes an instance of whatever type you want to throw. The moment you throw something, all the local variables in the current scope get deleted. You can use this to your advantage and the process of doing this has one of the worst names ever. I don’t blame you if you think this is strange. It is. The process is called RAII which stands for “resource allocation is initialization.” What it means is this. If you have some resource that needs to be cleaned up, maybe some memory that you allocated and needs to be deleted or a file that was opened and needs to be closed, then instead of working with the memory pointer directly or with the file handle directly, use a class that knows how to take ownership of the resource. This class will have a destructor that can clean up the resource. Since you know that when throwing something, all the local variables in scope will be destructed, then your RAII classes will also be destructed. And they can then perform the memory delete or the file close, or whatever else needs to be done.
This process of destructing local instances when unwinding the stack and looking for code that can catch the exception has an implied rule you should follow. Never let a destructor throw an exception. If destructors were allowed to throw exceptions, then what should happen when one exceptions was thrown and while destructing instances, another exception gets thrown? That would be bad. So don’t do it.
Once the immediate local variables are all cleaned up, the stack gets unwound one step at a time while looking for some code that can catch the exception type that was thrown. Each scope and each method that was called originally to get to the method that threw the exception becomes a step to be unwound on the stack.
You catch an exception by first declaring in your code that you’re entering a try block. Think of try like an if statement that takes no condition to be tested. A try block will instead test any exception that was thrown and not yet caught with each of one or more catch blocks that come after the try. Each catch block defines a single type that it can catch. This will usually be one of the standard exception types or a class derived from them. The catch blocks are tested in order. And the first catch that matches will be the one that handles the exception and stops any further search for an exception handler. This means that if you are catching both a standard exception type and a standard runtime_error exception and you put the catch for the base class exception first, then your code will not be able to handle specifically runtime_error exceptions because the exception catch block will match first. You should put more derived types first in your catch blocks to make sure they have a chance to handle their type.
Think of it like this, if somebody throws a ball to a group of people, then in order to have the best chance of catching that ball, each person should have a glove custom designed to fit the type of ball that was thrown. The person with the best fitting glove will be the one to catch the ball.
What happens when you catch an exception? What do you do? It’s going to depend on where you are in your code when the exception was caught. You might be in a position to retry the operation that failed. Or maybe all you can do is make an entry of what happened in the log file. Whatever you do, the one thing you need to decide is if you fully handled the exception or if you did only what you could and want to let some other code try to handle the same exception. If you don’t fully deal with an exception, then you can rethrow the same exception. You do this by just writing throw in your code. Don’t write throw with the variable name that you caught. That’s not a rethrow. That’s a brand new throw and is probably not what you wanted.