Exceptions and Debugging
The course is part of this learning path
In this course, we will discuss the topic of exceptions and debugging. We'll see that exceptions are objects representing exceptional situations and that these are usually problems in our code or in the environment in which our code is running. By using exception handling, we will know how to respond to problems when they arise.
- Learn the proper syntax and techniques to work with exceptions
- Understand inheritance in the context of Object-Oriented Programming
- Beginner coders, new to C++
- Developers looking to upskill by adding C++ to their CV
- College students and anyone studying C++
To get the most out of this course, you should have a basic understanding of the fundamentals of C++.
In this lecture, we will discuss exceptions and the exception hierarchy with a brief introduction to inheritance. So, that we will better understand how many of the different exception classes are related to one another.
An exception is an object in C++ that represents a response to an event, often one that could prevent the program from continuing to function, if not handled correctly. Exceptions help us to make our programs more robust, that is stronger, and more resilient to unusual occurrences. As a part of the increased robustness, exceptions tend to make our programs more fault-tolerant as well, meaning that when a serious problem occurs our programs can either recover and continue or can terminate gracefully. Graceful termination refers to our programs ending in a cleaner, more predictable way than they might if we didn't handle the exceptions. We've all probably experienced a situation where a program is running one minute and the next minute, we get a pop up with error codes and very technical-sounding jargon plastered on it, telling us what happened. When we were in control of our programs, even if the program must shut down and can't continue, we can choose how it shuts down. For example, to give the user a chance to save their work, or give them a more informative, less intimidating message than they might have received otherwise. As indicated, in some cases, you might even be able to handle the exception and make the program continue without further issues. So, how do exceptions fit into C++? As I mentioned, there are objects, so that means there must be exception classes. Many of the exceptions we will deal with in C++ are derived from a single class, actually named exception. What does it mean when I say one class is derived from another? Well, this requires a little bit of information and a little bit of review. Do you remember the section on object-oriented programming? I sure hope you do. So, think fast. What are the three primary principles of object-oriented programming? That's right. Encapsulation, inheritance, and polymorphism. So, what does that have to do with the current discussion? Well, practically everything. We won't do a substantial coverage of it now, but I do need to introduce you to some concepts related to inheritance. We are pros at encapsulation now, so inheritance is the next logical step. While encapsulation is focused on individual classes, inheritance is all about relationships between different classes. In languages with object-oriented features like C++, inheritance can be viewed as what we call an is-a relationship. For example, consider the diagram you see here. This is a domain-level UML class diagram where I'm not showing the data or behaviors of the classes involved, just the class names. We're interested in the relationships with this type of diagram. The unfilled arrowhead and the solid black line from each of the classes near the bottom represents inheritance between each of the dog, cat, and bird classes with the animal class.
In C++ we would say that the dog is a derived class of animal, and that animal is a base class for dog. Likewise, cat and bird are derived classes of animal, and animal is their base class. Sometimes you'll hear the terms parent and child used to refer to the relationship, or even superclass to refer to the base class such as animal, and subclass to refer to a derived class such as dog. As an aside, the terms base class and derived class are extremely popular in C++ and some other languages like C#. Whereas, if you work a lot with java developers, for instance, they will likely use the term superclass and subclass instead. Another way we can look at the classes in the diagram is saying that a dog is a, or is an, to be grammatically correct, animal. Dog is an animal, a cat is an animal, a bird is an animal. So, each of the derived classes possesses features of the animal class but also have special features of their own. Now, this lecture isn't intended to be a comprehensive treatment of inheritance. You'll get a much more thorough chance to look into inheritance later in the course. So, hopefully, this was a useful introduction to help understand exceptions better. And since I mentioned it, here is part of the exception hierarchy in C++. This is only part of the exception hierarchy as there are more levels and more classes involved, but you should notice that the exception class is the ancestor class in the hierarchy.
Two of the most popular direct children of the exception class are the logic_error and runtime_error classes. I want you to remember these two specifically, again, logic_error and runtime_error. It may help you with your general knowledge and also hint at a challenge at the end of this lecture. Anyway, the logic_error and runtime_error exception classes have children or derived classes of their own, but not all are shown here. We can however see two children of logic_error, namely invalid_argument and out_of_range. Note that there are more, but I've only included a couple with this diagram. Each of these exceptions signals different types of problems that can occur in our programs. We will look at some of these and others later in this section. For now, though, you might be wondering we've covered a lot of concepts but what do exceptions and exception handling look like? How do we go about handling the exceptions and making a program more robust? Many languages including C++, have some form of try-catch statement. You place the code that could potentially cause the exception inside the try block and then you place the code to handle exceptions and catch blocks. Let's write some reasonably simple code and see if we can make it blow up, in other words, crash when we run it. So, here's our first attempt with no exception handling. So, let's create a project called ExceptionFun1.
So, create new project, empty project and we'll call it ExceptionFun1. And we're just going to create a main.cpp and fill it in, we don't need an additional class yet. So, main.cpp. I'm going to include iostream, we're going to include string, and we're going to include vector and the skeleton of main. So, inside here, I'm going to create a vector of names of size five. So, when I pass it to this constructor, pass the number to this constructor rather, it makes a vector of size five, names.at(0). So, instead of doing push_back which could make the vector larger, I'm going to actually use the cells that are available of which there are five. So, names.at(0) is "John", names.at(1) is "Bob", names.at(2) is "Sally", names.at(3) is, we can include a "Karen" of course, names.at(4) = "Smitty". So, this should be all of the names that we can put inside of this vector. There are five of them, John, Bob, Sally, Karen, and Smitty. One, two, three, four, five. Let's print them out. String name and names, and print out the name, and done. Now, let's see if we can cause an exception after the printout. So, will this cause an exception? Is there going to be a problem with this code? Let's see, names.at(5) = "Tyler". Okay, let's capitalize it, keep consistent. Now, the vector is of size five, so keep your eye out on what's going to happen. So, I'm going to run the program without debugging and you see that we do get a debug error.
So, it says the abort() has been called, and press 'Retry' to debug the application. So, I'm going to hit abort(), and then we're going to close it. It got up to "Smitty" and then it crashed, after it printed it crashed right here. We know it crashed right there, it's not always as obvious where it crashes. But you can see we get this air pop-up and let's try running it with the debug or something we haven't really done before or done too much of. So, let's go to Debug, Start Debugging. And so we're in debug mode now and it gives us a little more information. Says, unhandled exception, and then it gives some wacky set of numbers. This is a memory address essentially, and then an ExceptionFun1, this is the name of the exe, and it tells us what kind of exception, it's a C++ exception: std::out_of_range. And then it tells us exactly the memory location where the out_of_range exception occurred. So, out_of_range exception, in this case, it's pointing exactly to the line where the offending code was found, right here. Although vectors can resize dynamically, they do so when we call methods like push_back. We allocated five cells initially in our vector and populated it, and then we directly tried to access a cell beyond the allocated indices. This is what's causing the problem. It's out of range of the allocated memory that our vector is using. So, how might we write code to handle the exception ourselves instead of this big exception handler popping up?
So, before doing so though, make sure you close the program when it's in debugging mode using this technique, we want to go to Debug and then you want to go to Stop Debugging. It doesn't always make it easy to just close it using the X here, sometimes it'll not let you touch it, so Debug, Stop Debugging. That's the way to properly exit Debug. So, we have to add one more library near the top. It's a good idea to, right here, stdexcept, so standard exceptions live in there. And what we're going to do is we're going to see what happens when we put some exception handling code around our problem here. So, I'm going to grab this, put this in here, so there's a try and then we put this catch here, and you should probably consider the syntax similar to something you've seen before. So, cout << err.what(), and then we have to return 0. So, this right here looks maybe like a parameter we would pass into a function, and we're passing it in by reference and as a const reference. So, this is pretty common way to do this. And again, out_of_range is just an object. So, this is more efficient, you could just put out_of_range, err but this is more efficient. To make sure we have access to the exception definitions, again you should note that we added the stdexcept library at the top. Note that the example is just so we can get our feet wet with this syntax.
When you can handle the problem locally on the function you're in. You know what the problem is and if it's caused by a coding issue rather than something out of your control, using exceptions is not the best solution. In this code, we would simply not try to access an element out of bounds. You can also use if-else statements to make sure that you were accessing elements in bounds. But I think the code sample is important so we can see how exception handling behaves and what the syntax looks like. All right, let's run this again, this time without debugging. Debug, start without debugging. Okay, there we go. So, nice, note that the what() method that we put it here in the catch right here, err.what(), the error that was thrown or the exception that was thrown has a what() method. And, that what method is printing out invalid vector subscript, interesting. So, it's like a parameter to a function, we put a const reference to the out_of_range object in the catch header. And although we don't make an explicit function call, the catch in many ways has syntax and concepts that are little like function definitions. Again, the header contains the exception you catch, just like a function would contain a parameter that is passed in. Hopefully, this lecture helped you to get at least a small peek into what exception handling is, why we want it, and the basic syntax associated with it.
Before we move on, I have a very small challenge for you. All I want you to do is out loud say the names of the two popular derived classes of the exception class that are used in C++. Pause the video, if you'd like, and repeat the two derived classes, child classes of the exception class that were really popular. Did you get it? Do you remember the two children of the exception class that we talked a lot about? Here's the answer, logic_error, and runtime_error. Awesome. Now that we have explored the very basics, let's move on to the next lecture to learn a little more about logic_errors in general and also where they apply to C++. I'll see you there.
John has a Ph.D. in Computer Science and is a professional software engineer and consultant, as well as a computer science university professor and department chair.