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
#include
#include
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>.
- bad_alloc
Thrown by new when a memory allocation error occurs
- out_of_range
Thrown by vector's at( ) function for an bad index parameter.
- invalid_argument
Thrown when the value of an argument (as in vector< int > intVector(-30);)
is invalid
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
#include
#include
using namespace std;
int main ( )
{
try
{
int size = -30;
vector 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