TUTORIAL: C++ Exceptions

  • spork
  • Brewmaster
  • Silver Member
  • User avatar
  • Posts: 6252
  • Loc: Seattle, WA

Post 3+ Months Ago

Introduction


Note: This tutorial assumes a basic knowledge of the C++ language. You should know how to use functions, variables, and have a basic understanding of how classes work.

Note: The complete source code for the examples in this tutorial can be downloaded at the end of the tutorial.

In this tutorial, we will cover the basics of handling exceptions in C++. We'll also touch on how to create and use your own exceptions in your applications, as well as how to deal with exceptions thrown by STL (Standard Template Library) classes.


Quick Reference


This is for people who are already familiar with exception in other object-oriented languages and simply need to know the syntax for using exceptions in C++. If you are completing this tutorial, please skip to the next section, "What is an exception?"

Throwing an exception:
CPP Code: [ Select ]
void someFunction() throw( int, MyException ) {
    throw 5;
    // or
    throw MyException();
}
  1. void someFunction() throw( int, MyException ) {
  2.     throw 5;
  3.     // or
  4.     throw MyException();
  5. }


Catching exceptions:
CPP Code: [ Select ]
try {
    someFunction();
}
catch( int ie ) {
    cout << "Caught an int: " << e << endl;
}
catch( MyException& me ) {
    cout << "Exception: " << me.getMessage() << endl;
}
catch( ... ) {
    cout << "Caught an unknown exception." << endl;
}
  1. try {
  2.     someFunction();
  3. }
  4. catch( int ie ) {
  5.     cout << "Caught an int: " << e << endl;
  6. }
  7. catch( MyException& me ) {
  8.     cout << "Exception: " << me.getMessage() << endl;
  9. }
  10. catch( ... ) {
  11.     cout << "Caught an unknown exception." << endl;
  12. }


Rethrowing an exception:
CPP Code: [ Select ]
try {
    someFunction();
}
catch( MyException& me ) {
    rethrow me;
}
catch( ... ) {
    rethrow;
}
  1. try {
  2.     someFunction();
  3. }
  4. catch( MyException& me ) {
  5.     rethrow me;
  6. }
  7. catch( ... ) {
  8.     rethrow;
  9. }



What is an exception?


An exception is a value that is thrown by a part of a program when an exceptional situation occurs. Normally, exceptions are used to indicate that an unexpected, but planned-for, error has occurred and should be dealt with by a different part of the program.

For example, a network socket could be maintaining a connection to a remote host, when the connection is unexpectedly terminated. The program, of course, should continue to function, but it needs to deal with the fact that the socket is no longer usable. The socket can throw an exception, which will be caught by a higher-lever part of the program, which will deal with the situation appropriately, for example, by creating a new socket or notifying the user.

The is some common terminology involved with exceptions that you should become familiar with. Let's take a look at the three most common C++ keywords used with exceptions:

try - We use a try block to surround parts of code that can generate exceptions. This section of code is specially examined during execution to watch for any exceptions that may have been thrown by calls to functions within the try block.

catch - Every try block will usually have one or more catch blocks following it. A catch block contains code that should be executed whenever an exception of a specific type is caught. This allows us to customize the behavior of our program based on what kinds of things could go wrong.

throw - This keyword serves two purposes. When used within the body of a function, it is used to throw an exception, passing it up the call stack. When used in the header of a function, it is used to specify what types of exceptions the function may throw.

rethrow - We use this to take an exception that we have caught in a catch block and throw it up the call stack. This is normally used when the function does not have the capability of dealing with the exception and should pass on the exception to the next function on the stack.

A complete description of what the call stack is and how it works is outside the scope of this tutorial. To facilitate this tutorial, however, I'll briefly describe the concept.

In a nutshell, every function that gets called in C++ has all of its local variables and information placed an a special area of memory called the stack. As you might have guessed, the data in this area is added and removed in the same fashion as things are removed to and from an ordinary stack data structure. Every time a function is called, it is placed on the stack. Once the function returns, it is removed from the stack. Main() will always be the first function added to the stack and the last one removed.

When an exception is thrown by the current function, it is passed to the next function on the stack. This function must deal with the exception, either by gracefully handling it via a catch block, or by passing it up the call stack to its calling function. In the case of the second situation, the exception will continue to propagate up the stack until it reaches a function that can successfully catch and deal with the exception. If no function handles the exception, it will arrive at main(). If main() does not catch and deal with the exception, the program will terminate.


Throwing exceptions


Now that you know what an exception is, let's look at how to throw exceptions in our own functions. Take a look at the following example:

CPP Code: [ Select ]
void doSomething() {
   
    throw std::string( "Error: something went wrong" );
}
  1. void doSomething() {
  2.    
  3.     throw std::string( "Error: something went wrong" );
  4. }


This simple function, doSomething(), is useless right now, but it serves as a basic example of how to throw an exception. Notice that the throw keyword is used to actually throw the exception. At this point, program execution will stop at that line, and the exception will be thrown up the call stack to whatever function invoked doSomething(). It is then up to that function to deal with the exception, which we will soon see.

Another important thing to take note of is the object being thrown. In this example, we are throwing a string as our exception. But we are not limited to this; in fact, you can throw data of virtually any type as an exception. Some types work much better than others, and in a moment we'll look at the advantages and disadvantages of using various types as exceptions.

Now, we would obviously never write a function like the one above, so let's look at a somewhat more plausible use of throwing an exception:

CPP Code: [ Select ]
float squareRoot( float num ) {
   
    float result = 0;
   
    if( num < 0 ) {
        throw std::string(
            "Error: number must not be negative" );
    }
   
    result = sqrt( num );
   
    return result;
}
  1. float squareRoot( float num ) {
  2.    
  3.     float result = 0;
  4.    
  5.     if( num < 0 ) {
  6.         throw std::string(
  7.             "Error: number must not be negative" );
  8.     }
  9.    
  10.     result = sqrt( num );
  11.    
  12.     return result;
  13. }


This function, squareRoot, takes a floating point number and returns the square root of it.

Note: sqrt() is a function found in the C math library, and can be used by including the <cmath> header.

Now, since we can't calculate the square root of a negative number, we should throw an exception if a negative number is passed into the function. In this case, we throw a string again so that we can use the exception as an error message.

Notice that we don't need to put the actual calculation, result = sqrt( num );, in an else if block. This is because if the number is negative, the exception will be thrown, and execution within the function will stop. The exception will be thrown, and program execution will be transfered to the calling function to deal with the exception.


What can be thrown?


As I mentioned earlier, virtually any data type in C++ can be thrown as an exception. For example, we could just as easily have thrown an integer in our doSomething() example:

CPP Code: [ Select ]
void doSomething() {
   
    throw 5;
}
  1. void doSomething() {
  2.    
  3.     throw 5;
  4. }


Or, we could have thrown, say, a boolean value:

CPP Code: [ Select ]
void doSomething() {
   
    bool someVariable = false;
    throw someVariable;
}
  1. void doSomething() {
  2.    
  3.     bool someVariable = false;
  4.     throw someVariable;
  5. }


These are both equally valid and will execute just fine. But the problem with using primitive data types as exceptions is that they provide very little information about what error actually occurred. When we throw an exception, it is useful to be able to include information about what actually went wrong in our application: the context, the state, extra information, and so forth.

Even throwing strings (C-style or STL, doesn't matter) isn't the best solution. The ideal exception would be able to contain all sorts of information that we specify without limiting us to the linear structure of a string. So what do we normally do? Create a class.

Remember, any data type in C++ can be thrown as an exception, and this includes any custom data types we create. Therefore, we can create classes to represent our exceptions, and these classes can contain as much or as little information as we like, structured however we like.

Let's take a look at a simple exception class, CustomException, that demonstrates this:

CPP Code: [ Select ]
class CustomException {
 
private:
   
    std::string message;
 
public:
    CustomException( std::string message );
    inline std::string getMessage() { return message; };
 
};
  1. class CustomException {
  2.  
  3. private:
  4.    
  5.     std::string message;
  6.  
  7. public:
  8.     CustomException( std::string message );
  9.     inline std::string getMessage() { return message; };
  10.  
  11. };


Here we have declared a simple class, CustomException. The class has one data member, message, which will hold the error message associated with our exception. We define a basic constructor that takes a string as an argument to set the message, and we have an accessor for message. Now, we can throw an instance of our CustomException class as an exception, like this:

CPP Code: [ Select ]
void doSomething() {
   
    throw CustomException( "An error occurred." );
}
  1. void doSomething() {
  2.    
  3.     throw CustomException( "An error occurred." );
  4. }


At first, this seems no more useful than throwing a lone string. Heck, it even requires us to write more code just to create the class! But look at the concept again. Now that our exception is represented as an entire object of our class rather than a primitive or a string, we can add lots and lots of information to our exception by adding additional data members to our class. For example, let's add a few more pieces of information to our class:

Code: [ Select ]
class CustomException {
 
private:
   
    std::string message;
    int errorCode;
    bool fatal;

 
public:
    CustomException( std::string message );
    inline std::string getMessage() { return message; };
    inline int getErrorCode() { return errorCode; };
    inline bool isFatal() { return fatal; };

 
};
  1. class CustomException {
  2.  
  3. private:
  4.    
  5.     std::string message;
  6.     int errorCode;
  7.     bool fatal;
  8.  
  9. public:
  10.     CustomException( std::string message );
  11.     inline std::string getMessage() { return message; };
  12.     inline int getErrorCode() { return errorCode; };
  13.     inline bool isFatal() { return fatal; };
  14.  
  15. };


Now that we've added additional data about the conditions and circumstances surrounding the problem that generated the exception, whatever function ends up handling the function will be better able to deal with it correctly.

The common design approach for exception classes is to define a base exception class that holds very basic information about a class. We would then create subclasses for different exception types, with each subclass containing relative information specific to each particular exception.

For example, say we wanted to create a new exception type, FileNotFoundException, for dealing with file I/O. We can simply extend our CustomException class and add relevant information:

Code: [ Select ]
class FileNotFoundException : public CustomException {
 
private:
   
    std::string filename;
 
public:
    FileNotFoundException( std::string filename );
    inline std::string getFilename() { return filename; };

   
    // Override the getMessage() method
    inline std::string getMessage() {
        std::stringstream ss;
        ss << "File not found: " << filename;
        return ss.str();
    }

   
    // note: all other member data and methods are inherited!
};
  1. class FileNotFoundException : public CustomException {
  2.  
  3. private:
  4.    
  5.     std::string filename;
  6.  
  7. public:
  8.     FileNotFoundException( std::string filename );
  9.     inline std::string getFilename() { return filename; };
  10.    
  11.     // Override the getMessage() method
  12.     inline std::string getMessage() {
  13.         std::stringstream ss;
  14.         ss << "File not found: " << filename;
  15.         return ss.str();
  16.     }
  17.    
  18.     // note: all other member data and methods are inherited!
  19. };


Now, we can throw a FileNotFoundException if we run into a situation where we are unable to open a file for reading or writing. Instead of initializing the exception with a message, we'll pass it the filename that couldn't be opened, and whenever we call getMessage(), our overridden version will print out "File not found: " and the filename. Neat!


Using try/catch blocks to handle exceptions


Now that we're comfortable throwing exceptions, let's take a look at how to catch and handle an exception that has been thrown.

In C++, we use a try block to surround code that has the potential to throw exceptions. Code within try blocks will function completely normally under non-exceptional circumstances.

Code: [ Select ]
try {
    std::cout << "Calling a function that may throw an exception..."
        << std::endl;
    doSomething();  // may throw an exception
}
  1. try {
  2.     std::cout << "Calling a function that may throw an exception..."
  3.         << std::endl;
  4.     doSomething();  // may throw an exception
  5. }


However, once an exception is thrown by a call to a function within the block, execution stops at that line within the block and is handed over to try's partner, the catch block. Let's add a catch block that catches any CustomException objects that may have been thrown by doSomething():

Code: [ Select ]
try {
    std::cout << "Calling a function that may throw an exception..."
        << std::endl;
    doSomething();  // may throw an exception
}
catch( CustomException e ) {
   
    std::cout << "Exception: " << e.getMessage();
}

  1. try {
  2.     std::cout << "Calling a function that may throw an exception..."
  3.         << std::endl;
  4.     doSomething();  // may throw an exception
  5. }
  6. catch( CustomException e ) {
  7.    
  8.     std::cout << "Exception: " << e.getMessage();
  9. }


Notice how the exception is transfered to the catch block and can be accessed via the CustomException variable e. (I chose to name the variable e, but you may choose any legal name you wish, just like with function arguments). Now we can access any necessary information contained in the exception, and execute code appropriately based on that information.

Catching by value vs. catching by reference


Now, the above code will compile and work, but there's a problem. Whenever we catch exceptions in the way seen above, we are catching by value. This concept has a direct relation to passing arguments to functions by value. Catching the exception by value means that every time the exception is passed up the call stack, a copy of the exception is made and sent to the next function. This leads to both a reduction in efficiency (large, complex exceptions classes may be very complex and take longer to copy) and a waste of resources (memory).

So what is the solution? As you might have guessed, we can catch the exception by reference rather than by value. The change in syntax should look very familiar to anyone who is used to passing function arguments by reference:

Code: [ Select ]
try {
    std::cout << "Calling a function that may throw an exception..."
        << std::endl;
    doSomething();  // may throw an exception
}
catch( CustomException& e ) {
   
    std::cout << "Exception: " << e.getMessage();
}
  1. try {
  2.     std::cout << "Calling a function that may throw an exception..."
  3.         << std::endl;
  4.     doSomething();  // may throw an exception
  5. }
  6. catch( CustomException& e ) {
  7.    
  8.     std::cout << "Exception: " << e.getMessage();
  9. }


Notice the change: e is declared as a reference to a CustomException instead of a regular CustomException variable.

Aside from performance and resource management, the main reason to catch exceptions by reference is to avoid data slicing. Data slicing occurs when an object of a certain class is copied into an object of one of its superclasses. When this happens, any data and behavior specific to the subclass is "sliced" off, any only the data of the superclass is preserved.

CPP Code: [ Select ]
try {
   
    // Assume that FileNotFoundException is a subclass
    // of CustomException
    throw FileNotFoundException( "example.txt" );
   
}
catch( CustomException e ) {
   
    std::cout << "Exception: " << e.getMessage();
}
  1. try {
  2.    
  3.     // Assume that FileNotFoundException is a subclass
  4.     // of CustomException
  5.     throw FileNotFoundException( "example.txt" );
  6.    
  7. }
  8. catch( CustomException e ) {
  9.    
  10.     std::cout << "Exception: " << e.getMessage();
  11. }


Consider the code above. What would be the output generated by the catch block in this case? Ideally, it would print out "File not found: ", followed by the filename, as specified in FileNotFoundException.getMessage(). But this is not the case! The problem is, all data specific to FileNotFoundException, such as the filename data member, will be sliced off so that the object fits into e, a CustomException.

To avoid this, we catch the exception by reference, which will ensure that all original information is preserved in the exception. We can then cast the reference to a FileNotFoundException reference, and access all appropriate members, including the correct version of getMessage().


Catching ...


We already know that we can specify many different catch blocks to handle exceptions of various types. But what if an exception arises that we are not prepared to catch? This can cause all sorts of undersired behavior in our programs. Ideally, we'd like to ensure that we cover all bases, including exceptions that we may have not been accepting. To do this, we use a special syntax to catch any remaining exceptions for which an existing catch block has not been specified: the "..." symbol.

Code: [ Select ]
try{
    doSomething();
}
catch( CustomException& ce ) {
    std::cout << "Exception: " << ce.getMessage();
}
catch( ... ) {
    std::cout << "An unknown exception was caught!";
}

  1. try{
  2.     doSomething();
  3. }
  4. catch( CustomException& ce ) {
  5.     std::cout << "Exception: " << ce.getMessage();
  6. }
  7. catch( ... ) {
  8.     std::cout << "An unknown exception was caught!";
  9. }


The syntax is very simple. Instead of specifying the type of exception to catch, we simple use ellipses (...) to indicate that this catch block should catch anything thrown that was not handled by any previous catch block. For this reason, we almost always put the catch( ... ) block at the end of all others, to ensure all exceptions are accounted for.

Note that we have no way of accessing the exception that is caught by this block. There is no way to get it's value(s) or to manipulate it.


Rethrowing an exception


There are many times when a function may catch an exception but is unable to deal with it appropriately. In this case, we usually want to pass the exception up the call stack to the next higher function to deal with it. We pass on this exception, or rethrow the exception, using the rethrow keyword:

Code: [ Select ]
try{
    doSomething();
}
catch( CustomException& ce ) {
    std::cout << "Exception: " << ce.getMessage();
    rethrow ce;
}
catch( ... ) {
    std::cout << "An unknown exception was caught!";
    rethrow;
}
  1. try{
  2.     doSomething();
  3. }
  4. catch( CustomException& ce ) {
  5.     std::cout << "Exception: " << ce.getMessage();
  6.     rethrow ce;
  7. }
  8. catch( ... ) {
  9.     std::cout << "An unknown exception was caught!";
  10.     rethrow;
  11. }


The syntax for rethrow is very simple. In the case where we are catching an exception of a known type, we simply rethrow the object that was caught, indicated by the variable used by the catch block. Similarly, in the case where an exception of an unknown type is caught using (...), we simply say rethrow. This should make sense since we do not have direct access to the object in this case. But don't worry, the compiler knows better, and the exception will be rethrown correctly.


Making promises (and keeping them!)



I mentioned earlier in this tutorial that the throw keyword has two uses. We've seen one of them: to throw an exception from within a function. Now let's take a look at the other use.

When we use the throw keyword in the header of a function, it indicates what kind of exceptions that function may throw. Thus, any type that does not appear in the throw() part of the header cannot and will not be thrown by that function. This is called a guarantee. In essence, you are making a promise to everyone who uses your function that there is a fixed list of exception types that your function may throw, and that these are the only types that need to be watched for in try blocks containing your function.

Let's revise our simple function, doSomething(), to include this information:

Code: [ Select ]
void doSomething(int value) throw( int, CustomException ) {
   
    if( value > 5 ) {
        throw CustomException( "An error occurred." );
    }
    else {
        throw 18;
    }
}
  1. void doSomething(int value) throw( int, CustomException ) {
  2.    
  3.     if( value > 5 ) {
  4.         throw CustomException( "An error occurred." );
  5.     }
  6.     else {
  7.         throw 18;
  8.     }
  9. }


Here we see that doSomething() is declared to be able to throw two types of exceptions: integers and CustomExceptions. Other functions who call this function now only need to write catch blocks for these two data types, and nothing more. doSomething() is promising that if it throws an exception, it will always be of one of those two types.

Note: a function can throw an exception of any type listed in the throw() clause of the function header, and any subclass of those types.

Now let's look at the three levels of exception guarantees, or exception safety, that a function can have:

Weak Guarantee
If the function throws an exception, no resources will leak as a result.

Strong Guarantee
If the function throws an exception, no resources are leaked and the program's state remains unchanged from what it was prior to the operation.

No-throw Guarantee
The function guarantees that no exceptions will be thrown from it, and that operation will always succeed under all circumstances.

Of course, we'd like to be able to have no-throw guarantees for all of our functions, but realistically this is just not possible in most systems. Although it is the best guarantee to have, it is often very difficult to achieve.

Let's revise our simple function, doSomething(), one last time to give it a no-throw guarantee:

Code: [ Select ]
void doSomething(int value) throw() {
   
    value = 0;
}
  1. void doSomething(int value) throw() {
  2.    
  3.     value = 0;
  4. }


Notice that to ensure a no-throw guarantee, we use an empty throw() clause to indicate that this function cannot and will not throw any type of exception.


STL exceptions


The STL contains an exception class, std::exception, that serves as the base class for all exceptions thrown by STL classes and functions. Similar to our getMessage() method in our own CustomException class, the STL exception class contains a method called what() that can be used to get the error message associated with the exception. When using STL classes, it's often a good idea to catch this exception in the event of something going wrong:

Code: [ Select ]
try {
    std::cout << "cout is a std::ostream object and could throw an exception";
}
catch( std::exception& ex ) {
    std::cerr << "Exception: " << ex.what();
}

  1. try {
  2.     std::cout << "cout is a std::ostream object and could throw an exception";
  3. }
  4. catch( std::exception& ex ) {
  5.     std::cerr << "Exception: " << ex.what();
  6. }


Additionally, we can modify our own CustomException class to extend the STL exception class, and thus use the same catch block to catch all of our derived exceptions as well. I'll leave that exercise up to you as you play around with your new knowledge of exceptions.


Conclusion


You should now have a solid understanding of how to implement and use exceptions in C++. You should understand the concepts of throwing, catching, and rethrowing exceptions, as well as the different guarantees that functions can make about exceptions.

I always welcome questions or feedback about this tutorial. Simply post a reply or PM me; I'm glad to help!
Attachments:
Exceptions.zip

(1.73 KiB) Downloaded 858 times

Complete source code for various examples using exceptions, including the CustomException class.

  • Anonymous
  • Bot
  • No Avatar
  • Posts: ?
  • Loc: Ozzuland
  • Status: Online

Post 3+ Months Ago

Post Information

  • Total Posts in this topic: 1 post
  • Moderator: Tutorial Writers
  • Users browsing this forum: No registered users and 3 guests
  • You cannot post new topics in this forum
  • You cannot reply to topics in this forum
  • You cannot edit your posts in this forum
  • You cannot delete your posts in this forum
  • You cannot post attachments in this forum
 
 

© 1998-2014. Ozzu® is a registered trademark of Unmelted, LLC.