More Exceptions

Exception Classes

An exception can throw a value of any type -- int, float, etc. or it can throw an object. It's common to define objects that contain information that you to pass back to the caller. It's also common to define a different exception class for each exception type. The class name should describe the error, not the code that throws the error.

Typically the class contains some basic information and or message. One or more constructors and accessors will also be required. This class might be thrown when a negative value was passed as a parameter that was expected to be non-negative. It contains the name of the parameter and the actual value passed to the function.

class NegativeParameter { public: NegativeParameter( const string& parameter, int value ); int GetValue ( ) const; const string& GetParameter( ) const; private: int m_value; string m_paramName; }; This class might be thrown in a function like this void SomeFunction (int age) { if (age < 0) throw NegativeParameter( "age", age); // the rest of the code }

At the far end of the spectrum, an exception class contains lots more information and hence lots more methods. At the other end of the spectrum, an exception class may be a trivial class such as this

class MyException { }; In this case, the caller gets no other information other than the fact that an exception of this type occurred, which is presumably all the caller needs.

Exception Specification

A function's prototype may specify which exceptions the function throws or that throws none at all. // Divide() throws only the DivideByZero exception void Divide (int dividend, int divisor ) throw (DivideByZero); // This function throws either DivideByZero or AnotherException void Divide (int dividend, int divisor ) throw (DivideByZero, AnotherException); // This function promises not to throw any exception void Divide (int dividend, int divisor ) throw ( ); // This function may throw any exception it wants void Divide (int dividend, int divisor ); There is some debate as to whether exception specification is a good thing or not. Exception specifications are treated differently by different compilers. Exception specifications must be maintained by the programmer if/when the function changes. If the function throws an exception that's not specified (perhaps by another function), then an exception of type unexpected() is thrown. Exception specifications are a good source of documentation. Our course standards do not require exception specifications.

Exceptions in Constructors

If a constructor fails, throwing an exception is the best course of action. All fully constructed automatic objects are destroyed, but dynamically constructed objects are left in an unknown state. Here's a simple example #include <vector> #include <iostream> #include <stdexcept> using namespace std; // Trivial exception class class NotConstructed { }; // A simple class as an example class MyClass { public: MyClass( int value = 42 ); ~MyClass ( ); int GetValue( ) const; private: int *m_pValue; }; // MyClass constructor MyClass::MyClass ( int value ) { m_pValue = new int(value); // pretend something bad happened throw NotConstructed( ); } // MyClass destructor MyClass::~MyClass ( ) { delete m_pValue; } // MyClass Accessor int MyClass::GetValue( ) const { return *m_pValue; } //---------------------------- // a simple main using MyClass int main ( ) { try { MyClass bob; MyClass mary; cout << bob.GetValue( ) << endl; cout << mary.GetValue( ) << endl; } catch (NotConstructed& exception) { cout << "MyClass was not constructed" << endl; } return 0; } We can also throw an exception for inconsistent parameters such as trying to set a Date to February 30th // a Date constructor Date::Date (int month, int day, int year) m_month (month) , m_day(day) , m_year( year) { // check the logic of the date here, and throw a bad date exception } Classes which employ aggregation work properly in the face of exceptions which might occur during construction. For example, suppose that class A is composed of classes, B, C and D. When an A object is instantiated, the B, C, and D "subobjects" are constructed first. If any of these fail, objects which were successfully constructed are destroyed. The A object is considered never to have existed and so its destructor is not called. If construction of any base or member subobject fails, the construction of the whole object must fail.

Exceptions in a Destructor

The general rule is "Don't throw exceptions in a destructor".

The problem is that your object may be destroyed as the result of some other exception. If your destructor now throws another exception, there's a real problem. Should the C++ runtime system handle your exception (and cease handling the original exception) or ignore your exception and continue handling the original situation.

Standard C++ Library Exceptions

The C++ library has some exceptions already defined. A couple of the common one's you're likely to see are listed below. Include the header <stdexcept>. Each of these exceptions is derived from the standard exception base class std::exception. You can use this base class in your catch blocks (catch (std::exception)) and as base class for your own exception classes. std::exception supports a public method known as what( ) which returns a string that can be printed. Try this code and see what happens, then edit it and play some more. #include <stdexcept> #include <vector> #include <iostream> using namespace std; int main ( ) { try { int size = -30; vector <int> iVector(size); // should throw an exception int x = iVector.at( 44 ); cout << x << endl; } catch (exception& e) { cout << e.what( ) << endl; } catch (bad_alloc& e) { cout << e.what() << endl; } catch (invalid_argument& exception) { cout << exception.what( ) << endl; } catch (out_of_range& exception) { cout << exception.what( ) << endl; } return 0; }


Last Modified: Monday, 28-Aug-2006 10:16:05 EDT