Creating an Exception Class

Problem

You want to create your own exception class for tHRowing and catching.

Solution

You can throw or catch any C++ type that lives up to some simple requirements, namely that it has a valid copy constructor and destructor. Exceptions are a complicated subject though, so there are a number of things to consider when designing a class to represent exceptional circumstances. Example 9-1 shows what a simple exception class might look like.

Example 9-1. A simple exception class

#include 
#include 

using namespace std;

class Exception {

public:
 Exception(const string& msg) : msg_(msg) {}
 ~Exception( ) {}

 string getMessage( ) const {return(msg_);}
private:
 string msg_;
};

void f( ) {
 throw(Exception("Mr. Sulu"));
}

int main( ) {

 try {
 f( );
 }
 catch(Exception& e) {
 cout << "You threw an exception: " << e.getMessage( ) << endl;
 }
}

 

Discussion

C++ supports exceptions with three keywords: try, catch, and tHRow. The syntax looks like this:

try {
 // Something that may call "throw", e.g.
 throw(Exception("Uh-oh"));
}
catch(Exception& e) {
 // Do something useful with e
}

An exception in C++ (Java and C# are similar) is a way to put a message in a bottle at some point in a program, abandon ship, and hope that someone is looking for your message somewhere down the call stack. It is an alternative to other, simpler techniques, such as returning an error code or message. The semantics of using exceptions (e.g., "trying" something, "throwing" an exception, and subsequently "catching" it) are distinct from other kinds of C++ operations, so before I describe how to create an exception class I will give a short overview of what an exception is and what it means to tHRow or catch one.

When an exceptional situation arises, and you think the calling code should be made aware of it, you can stuff your message in the bottle with the tHRow statement, as in:

throw(Exception("Something went wrong"));

When you do this, the runtime environment constructs an Exception object, then it begins unwinding the call stack until it finds a try block that has been entered but not yet exited. If the runtime environment never finds one, meaning it gets all the way to main (or the top-level scope in the current thread) and can't unwind the stack any further, a special global function named terminate is called. But if it does find a try block, it then looks at each of the catch statements for that try block for one that is catching something with the same type as what was just thrown. Something like this would suffice:

catch(Exception& e) { //...

At this point, a new Exception is created with Exception's copy constructor from the one that was thrown. (The one in scope at the throw is a temporary, so the compiler may optimize it away.) The original exception is destroyed since it has gone out of scope, and the body of the catch statement is executed.

If, within the body of the catch statement, you want to propagate the exception that you just caught, you can call throw with no arguments:

throw;

This will continue the exception handling process down the call stack until another matching handler is found. This permits each scope to catch the exception and do something useful with it, then re-tHRow it when it is done (or not).

That's a crash course in how exceptions are thrown and caught. Now that you're equipped with that knowledge, consider Example 9-1. You can construct an Exception with a character pointer or a string, and then throw it. But this class is not terribly useful, because it is little more than a wrapper to a text message. As a matter of fact, you could get nearly the same results by just using a string as your exception object instead:

try {
 throw(string("Something went wrong!"));
}
catch (string& s) {
 cout << "The exception was: " << s << endl;
}

Not that this is necessarily a good approach; my goal is to demonstrate the nature of an exception: that it can be any C++ type. You can throw an int, char, class, struct, or any other C++ type if you really want to. But you're better off using a hierarchy of exception classes, either those in the standard library or your own hierarchy.

One of the biggest advantages to using an exception class hierarchy is that it allows you to express the nature of the exceptional circumstance with the type of exception class itself, rather than an error code, text string, severity level, or something else. This is what the standard library has done with the standard exceptions defined in . The base class for the exceptions in is exception, which is actually defined in . Figure 9-1 shows the class hierarchy for the standard exception classes.

Figure 9-1. The standard exception hierarchy

Each standard exception class, by its name, indicates what category of condition it is meant to identify. For example, the class logic_error represents circumstances that should have been caught during code writing or review, and its subclasses represent subcategories of that: situations such as violating a precondition, supplying an out-of-range index, offering an invalid argument, etc. The complementary case to a logical error is a runtime error, which is represented by runtime_error. This indicates situations that, more than likely, could not have been caught at code time such as range, overflow, or underflow.

This is a limited set of exceptional situations, and the standard exception classes probably don't have everything you want. Chances are you want something more application-specific like database_error, network_error, painting_error and so on. I will discuss this more later. Before that, though, let's talk about how the standard exceptions work.

Since the standard library uses the standard exception classes (imagine that), you can expect classes in the standard library to throw one when there is a problem, as in trying to reference an index beyond the end of a vector:

std::vector v;
int i = -1;

// fill up v...

try {
 i = v.at(v.size( )); // One past the end
}
catch (std::out_of_range& e) {
 std::cerr << "Whoa, exception thrown: " << e.what( ) << '
';
}

vector<>::at will throw an out_of_range exception if you give it an index that is less than zero or greater than size( ) - 1. Since you know this, you can write a handler to deal with this kind of exceptional situation specifically. If you're not expecting a specific exception, but instead would rather handle all exceptions the same way, you can catch the base class for all exceptions:

catch(std::exception& e) {
 std::cerr << "Nonspecific exception: " << e.what( ) << '
';
}

Doing so will catch any derived class of exception. what is a virtual member function that provides an implementation-defined message string.

I am about to come full circle. The point of Example 9-1 followed by so much discussion is to illustrate the good parts of an exception class. There are two things that make an exception class useful: a hierarchy where the class communicates the nature of the exception and a message for the catcher to display for human consumers. The exception class hierarchy will permit developers who are using your library to write safe code and debug it easily, and the message text will allow those same developers to present a meaningful error message to end-users of the application.

Exceptions are a complicated topic, and handling exceptional circumstances safely and effectively is one of the most difficult parts of software engineering, in general, and C++, in particular. How do you write a constructor that won't leak memory if an exception is thrown in its body, or its initializer list? What does exception-safety mean? I will answer these and other questions in the recipes that follow.

Building C++ Applications

Code Organization

Numbers

Strings and Text

Dates and Times

Managing Data with Containers

Algorithms

Classes

Exceptions and Safety

Streams and Files

Science and Mathematics

Multithreading

Internationalization

XML

Miscellaneous

Index



C++ Cookbook
Secure Programming Cookbook for C and C++: Recipes for Cryptography, Authentication, Input Validation & More
ISBN: 0596003943
EAN: 2147483647
Year: 2006
Pages: 241

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net