tutorials.neonphog.com icon

c++ - Exceptions

Introduction Get the Source Compiling and Running It Walkthrough Conclusion

Introduction

Consider a hash table class that stores and returns integers by string keys. One option would be to include a function for checking if a particular key was valid, and a separate function for getting a value. You have just probed the hash table twice, slowing down your program. Another option would be to include a function to say if the last retrieval operation was a success, or if it returned invalid data (i.e. 0). This reduces the clarity of your code. Or you could simply call for the data. If it is not there, the hash table will throw an exception which you can deal with.

Exceptions are an important part of modern programming. In C++, they may add a slight amount to the memory footprint of your program, but in terms of runtime execution, they are often faster than non exception alternatives (see side-note). When you add the benefits of code clarity, I think exceptions are the way to go.

This tutorial will illustrate the writing of custom exception classes, show how to throw these new classes, and how to catch them.

Get the Source

except.h [view]

except.cpp [view]

Compiling and Running It

1) Compile

Compile the source code with the following commands:

g++ -c -o except.o except.cpp
g++ -o except except.o

2) Run It

Running the resulting executable should produce output of the following variety:

$ ./except
0: Caught a basic FruitException: 
        FruitException
1: Caught an AppleException: 
        FruitException: Apples are rotten
2: Caught a basic FruitException: 
        FruitException: Bananas are getting brown
3: Got away clean!
$

Walkthrough

1) The FruitException Class

class FruitException: public std::exception
{
public:
	FruitException();
	virtual ~FruitException() throw();
	virtual const char *what() const throw();

protected:
	std::string sWhat;
};
The "throw()" after the function declarations indicates that these functions are not allowed to throw exceptions ("throw(int)" would say they were able to throw integers). It would not be good for the functions in our exception class to throw any exceptions.

We derive our class from the "std::exception" class, which provides a "what" function that returns a null-terminated "const char *" explaining the exception.

We are also going to allocate some space to store this "what" information in the form of a std::string "sWhat". By making this class variable "protected" we allow our child classes access to it, without exposing it to the public.

2) The AppleException Class

class AppleException: public FruitException
{
public:
	AppleException(std::string sDetail);
	virtual ~AppleException() throw();
};
Our constructors are not set to "throw()" (no exceptions) because they *can* throw exceptions. Specifically std::bad_alloc indicating we ran out of memory trying to allocate the description string.

The AppleException class sub-classes FruitException. This time, we are going to take an additional string on the constructor to give more information about the specific circumstances that produced the exception. Note: we do not re-implement the "what" function, the function we wrote in FruitException will suffice.

The BananaException class is nearly identical to the AppleException class.

3) FruitException Code

FruitException::FruitException():
	sWhat("FruitException")
{
}

FruitException::~FruitException() throw()
{
}

const char *FruitException::what() const throw()
{
	return sWhat.c_str();
}

In the constructor for FruitException, we set our "sWhat" string to "FruitException". Our destructor does nothing special. In the "what" function, we simply return a "char *" version of the data in "sWhat".

4) AppleException Code

AppleException::AppleException(std::string sDetail)
{
	sWhat += ": Apples are ";
	sWhat += sDetail;
}

AppleException::~AppleException() throw()
{
}

In the AppleException constructor, our "sWhat" has already been set to the string "FruitException" by our parent class. We are going to append some more data to that string, including the detail information that was passed into this constructor ("sDetail").

The "what()" function in FruitException will simply return all the data in sWhat, including the data we have appended to it.

5) Throwing the exceptions

throw FruitException();
throw AppleException("rotten");
throw BananaException("getting brown");

When it comes time to throw your exceptions, you can just create them in line with the throw keyword as shown. The apple and banana exceptions take a single string intended to describe the circumstances of the exception.

6) Catching the exceptions

try
{
	//exceptions get thrown here...
}
catch( AppleException &e )
{
	std::cout << "Caught an AppleException: " << std::endl;
	std::cout << "\t" << e.what() << std::endl;
}
catch( FruitException &e )
{
	std::cout << "Caught a basic FruitException: " << std::endl;
	std::cout << "\t" << e.what() << std::endl;
}

When a section of your code is likely to generate an exception that you wish to catch, wrap it in a "try" block and create "catch" blocks for the exceptions you wish to trap. Note in the above code that we did not create a specific catch block for "BananaException"s. They will be caught in the "FruitException" block as Bananas are a child class of FruitException. Indeed when a BananaException is thrown in our test program, we saw the result was:

2: Caught a basic FruitException: 
        FruitException: Bananas are getting brown

Conclusion

With this template you should be able to create exception classes to be thrown and caught in your code. Before you go write a bunch of classes, however, take a moment to consider the structure of your class hierarchy. What structure makes the most sense, and is going to be the most useful to the people who are going to be catching your exceptions?

tutorials home c++ home