|
If anything can possibly go wrong, it will.
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
int& Array::Item(int index)
{
if( index<0 || index>=size ){
out_of_range err("index is out of range in Array::Item(int)");
throw err;
}
return data[index];
}
where the out_of_range is a standard exception class declared in the
try{
statements; // code for with we want to sat the handlers
}
catch( type_1 value_1 ){
statements; // code for handling exceptions type_1
}
[ catch( type_2 value_2 ){
statements; // code for handling exceptions type_2
} ]
...
[ catch( type_n value_n ){
statements; // code for handling exceptions type_n
} ]
For example, we can use the following code to detect out of range exceptions:
Array a;
try{
cout<<"count = "<<a.Count()<<endl;
cout<<" size = "<<a.Size()<<endl;
a.Item(10) = 5;
cout<<"a.Item(10) = "<<a.Item(10) = 5;
}
catch(out_of_range& e){
cerr<<"The following error has been detected:\n\t"<<e.what()<<endl;
}
This code will execute correctly down to the line
a.Item(10) = 5;method Item() will throw an exception because the value 10 is outside of the allowed index values (0 - 9). Thus, method Item() will be immediately terminated and the program will locate the handler for the exceptions of the out_of_range type. A handler for exactly this type of exception is specified in the catch clause; that is, the line
cerr<<"The following error has been detected:\n\t"<<e.what()<<endl;will be executed.
Technically speaking, we can throw any type of value, not necessarily an object of the exception class or any derived class. We can even throw a value of a primitive type like int or bool:
try{
cout<<"Throwing an integer value (7) ...\n";
throw 7;
cout<<"You shouldn't see this line";
}
catch(int i){
cout<<"Value "<<i<<" cought\n";
}
However, these kind of values do not contain enough information about the error happened. For this reason, it's
traditional to throw objects that are either derived from class exception (directly or indirectly) or
programmer created classes designed for the same purpose. The following diagram contains the hierarchy of standard
exception types in C++.
class CustomDefinedException : public logic_error {
public:
CustomDefinedException(const string &msg) : logic_error(msg) {};
};
Now you can throw the newly created exception:
if( gpa < 1.2 )
throw CustomDefinedException("GPA is too low");
Please note that this exception can be caught by
catch( CustomDefinedException &e ){ cerr<<"Error: "<<e.what; }
or as logic_error exception by
catch( logic_error &e ){ cerr<<"Error: "<<e.what; }
or even as exception
catch( exception &e ){ cerr<<"Error: "<<e.what; }
We also can catch different types of exceptions by putting several catch clauses. For instance, the following example catches user defined exceptions, logic error exceptions, and any other exception:
try{
statements;
}
catch( CustomDefinedException &e ){
cerr<<"Error: "<<e.what;
}
catch( logic_error &e ){
cerr<<"Error: "<<e.what;
}
catch( exception &e ){
cerr<<"Error: "<<e.what;
}
Please note that the order of these catch clauses is important. In the code above if an exception thrown,
it's first tested on being a CustomDefinedException exception, if it's not, then it's tested on being
logic_error exception, etc. If we put the logic_error catch first, then any
custom defined exception will be identified as logic_error exception because it's derived from the
logic_error class.
You don't need to have catch clauses for all possible types of exceptions, if an appropriate handler is not
found, the program keeps searching for a handler in the caller, etc. However, if you want to catch all possible
exceptions no matter if they are derived from exception class or not, you can use special
try{
statements;
}
catch( CustomDefinedException &e ){
cerr<<"Error: "<<e.what;
}
catch( logic_error &e ){
cerr<<"Error: "<<e.what;
}
catch(...){
cerr<<"Unknow exception caught.";
}
The throw command can be used without any argument inside a catch clause. In this case, this command re-throws the same exception to the caller's exception handler. The following example, shows how to re-throw an exception from a function to the function's caller:
int rethrow_exception()
{
try{
cout<<"Function rethrow_exception() is invoked\n";
throw domain_error("just throw any excepiton");
}
catch(exception &e){
cerr<<"Something went wrong: "<<e.what();
throw; // rethrow the exception
}
}
void main()
{
try{
rethrow_exception();
}
catch(domain_error &e){
cerr<<"Caught an exception in the main() function: "<<e.what();
}
}
HashArray::HashArray(int size)
{
try{
keys = new string[size];
values = new int[size];
}
catch( bad_allocation &e ){
cerr<<"Cannot allocate enough memory in HashArray("<<size<<")";
throw;
}
}
Why? To answer the question let's think what happens if we successfully executed the first new operator, but
not the second one. Obviously, an exception will be thrown, caught inside the constructor, and then re-thrown to report
about the problem. However, since the constructor was terminated and not successfully finished, the object is not yet
created. That means, that the destructor deallocating memory will not be invoked. Thus, the memory, allocated for the
private keys stays allocated. In order to fix the constructor, we need to deallocate the memory inside
the constructor's catch:
HashArray::HashArray(int size) : keys(NULL), values(NULL)
{
try{
keys = new string[size];
values = new int[size];
}
catch( bad_allocation &e ){
delete[] keys;
delete[] values;
throw;
}
}
We also need to be careful throwing exceptions inside destructors. Many destructors are invoked when the
try{
statements; // code for with we want to sat the handlers
}
catch( type_1 value_1 ){
statements; // code for handling exceptions type_1
}
[ catch( type_2 value_2 ){
statements; // code for handling exceptions type_2
} ]
...
[ catch( type_n value_n ){
statements; // code for handling exceptions type_n
} ]
[ __finally{
statements; // this code will be executed anyways
} ]
Statements in the __finally clause will be executed no matter was an exception thrown or not. In fact, this
code will be executed even an exception was thrown and not caught. If everything in the try clause goes fine,
then the code in the __finally block is executed immediately after the last statement in the try
block. If something goes wrong, and an exception occurs, then program searches for an appropriate exception handler and
if it's found, then the corresponding catch statements are executed, and then the __finally block;
if a handler not found, then the __finally block is run, and only then the program continues its search for
an exception handler. Please study the example program on the example page.
This property of the __finally block makes it a perfect place to release resources allocated in the corresponding try block. Please note that programs containing the __finally block should be compiled with the /clr key (compile for the common language runtime).
Mircosoft also provides its own exception hierarchy. The new base class Exception is defined and all other exception classes are derived from this base class. In this document we provide a list of MS exception classes. For complete information please consult MSDN library.