Exceptions
Contents
Procedural Style
In a procedural language the error handling is either done by checking the return value of a function or checking some other variable that contains the error. The pseudo code can look like: result = function1() ; if ( result < 0 ) //Handle error function2() ; if ( ErrorCode == 100 ) //Handle error The code can easily become very messy. Another way to handle errors and this is usually a feature in object oriented programming is to use Exceptions. The above will instead look like: try { result = function1() ; function2() ; } catch ( Exception except ) { //Any error in the try block causes control to come //to the catch block. } The code that can "throw" an error is enclosed in a "try" block. Immediately following the "try" block we have a "catch" block. In the "catch" block we can handle the error. We need to have the "catch" following the "try" block else we will get a compiler error. Both the "try" and "catch" work hand in hand.
Object Oriented Style
Basics
File: except1.cpp
#include <iostream> #include <fstream> #include <string> using namespace std; int ErrorCode = 0 ; int divideOperation1( int x1, int y1 ) { ErrorCode = 0 ; if ( y1 == 0 ) { ErrorCode = -1 ; return 0 ; } return ( x1 / y1 ) ; } int divideOperation2( int x1, int y1 ) { ErrorCode = 0 ; if ( y1 == 0 ) { throw "Divide by zero." ; } return ( x1 / y1 ) ; } int main() { divideOperation1 ( 4 , 0 ) ; if ( ErrorCode == -1 ) cout << "Error divisor is zero." << endl ; try { divideOperation2 ( 4 , 0 ) ; } catch ( const char* str1 ) //catch ( const string& str1 ) { cout << str1 << endl ; } return 0; }
Output: $ ./a.exe Error divisor is zero. Divide by zero. This program shows the old way of handling errors using the function: "divideOperation1" and the new way in the function ""divideOperation2" . if ( y1 == 0 ) { throw "Divide by zero." ; } This code is inside the function "function2" . What happens when "throw" is encountered ? In this case there is no catch otherwise control will move to the catch part. The system will clean up any local objects and the function will not execute any more code and the exception being thrown "const char*" is thrown to the calling code. try { divideOperation2 ( 4 , 0 ) ; } //catch ( const string& str1 ) catch ( const char* str1 ) { cout << str1 << endl ; } The "divideOperation2" in the calling code is enclosed in a "try" block so if it throws an exception the control comes to the catch block. If the object being thrown matches the catch argument then the catch code is executed. If the argument is not caught then it will be passed to the calling function. However in the above case we are at the "main" function and so the program will abort as demonstrated by the below program.
File: except2.cpp
#include <iostream> #include <fstream> #include <string> using namespace std; int ErrorCode = 0 ; int divideOperation1( int x1, int y1 ) { ErrorCode = 0 ; if ( y1 == 0 ) { ErrorCode = -1 ; return 0 ; } return ( x1 / y1 ) ; } int divideOperation2( int x1, int y1 ) { ErrorCode = 0 ; if ( y1 == 0 ) { throw "Divide by zero." ; //throw String("Divide by zero.") ; } return ( x1 / y1 ) ; } int main() { divideOperation1 ( 4 , 0 ) ; if ( ErrorCode == -1 ) cout << "Error divisor is zero." << endl ; divideOperation2 ( 4 , 0 ) ; cout << "End of Main." << endl ; return 0; } $ g++ except2.cpp ; ./a.exe Error divisor is zero. terminate called after throwing an instance of 'char const*' Aborted (core dumped) In the above code we have modified "except2.cpp" to have the statements: divideOperation2 ( 4 , 0 ) ; cout << "End of Main." << endl ; Since the exception is not caught and we are in the "main" function the system will not execute any more statements ( "End of Main" is not executed ) and the program will abort as shown in the output.There is a slight disadvantage to the exception handling and that is the control moves to the catch block and it may be difficult to retry the function calls as we are out of the normal coding block.
Multiple Catch
We can also have multiple catch statements corresponding to our try block.
File: except3.cpp
#include <iostream> #include <fstream> using namespace std; int ErrorCode = 0 ; int divideOperation1 ( int x1, int y1 ) { ErrorCode = 0 ; if ( y1 == 0 ) { ErrorCode = -1 ; return 0 ; } return ( x1 / y1 ) ; } int divideOperation2( int x1, int y1 ) { ErrorCode = 0 ; if ( y1 == 0 ) { throw "Divide by zero." ; } return ( x1 / y1 ) ; } int main() { divideOperation1 ( 4 , 0 ) ; if ( ErrorCode == -1 ) cout << "Error divisor is zero." << endl ; // throw 1 ; try { throw 5 ; //throw statement can occur anywhere divideOperation2 ( 4 , 0 ) ; } catch ( const char* str1 ) { cout << str1 << endl ; } //Reference to avoid a copy catch ( int& e1 ) { cout << "Integer Exception" << endl ; return 1 ; } return 0; }
Output: $ g++ except3.cpp ; ./a.exe Error divisor is zero. Integer Exception./a.exe We have 2 catch statements that correspond to the try block. One is to catch strings and the other is to catch an integer. The "divideOperation2" with the divisor of zero in this case would have thrown an exception but we never reach that line because the "throw 5" is executed.
Nested Catch
We can have nested try catch blocks also.File: except4.cpp
#include <iostream> #include <fstream> using namespace std; void function2() { throw 10 ; } void function1() { function2() ; } int main() { try { try { function1() ; } catch ( int& e1 ) { cout << "Integer Exception" << endl ; throw e1 ; } } catch( int& e2 ) { cout << "Outer Integer Exception" << endl ; } return 0; }
Output: $ g++ except4.cpp ; ./a.exe Integer Exception Outer Integer ExceptionExercises
1)
File: ex1.cpp
2)
File: ex2.cpp
General Exception
We can use the "..." to catch an exception of any type .File: except5.cpp
#include <iostream> #include <fstream> using namespace std; int main() { try { throw "Some error" ; } catch ( int& e1 ) { cout << "Integer Exception" << endl ; } catch( ... ) { cout << "Default Handler." << endl ; } return 0; }
Output: $ g++ except5.cpp ; ./a.exe Default Handler. The notation catch( ... ) means the catch can handle any type. We can also catch other specific exceptions as shown in the code at the same time. This may be useful if we are calling other functions that
Custom Exception
We can also throw an object of our own class.File: except6.cpp
#include <iostream> #include <fstream> using namespace std; class MyException { public: string messg ; MyException( string str1 ) { messg = str1 ; } }; int main() { try { throw MyException("Error") ; } catch ( MyException& e1 ) { cout << e1.messg << endl ; } return 0; } $ g++ except6.cpp ; ./a.exe Error Notice we have been using the reference in the catch statement catch ( MyException& e1 ) The below program shows the difference between using regular objects instead of references.
File: scope1.cpp
#include <iostream> #include <fstream> using namespace std; class MyException { public: string messg ; MyException( string str1 ) { messg = str1 ; cout << "MyException: constructor: " << messg << endl ; } MyException( const MyException& obj1 ) { messg = obj1.messg ; cout << "MyException: copy constructor: " << messg << endl ; } ~MyException( ) { cout << "MyException: destructor: " << messg << endl ; } }; int main() { try { MyException error1("Error 1:") ; throw MyException("Error 2:") ; } catch ( MyException error2 ) { cout << error2.messg << endl ; } cout << "Step 1:" << endl ; try { throw MyException("Error 3:") ; } catch( MyException& error3 ) { cout << error3.messg << endl ; } cout << "Step 2:" << endl ; return 0; } $ g++ scope1.cpp ; ./a.exe MyException: constructor: Error 1: MyException: constructor: Error 2: MyException: destructor: Error 1: MyException: copy constructor: Error 2: Error 2: MyException: destructor: Error 2: MyException: destructor: Error 2: Step 1: MyException: constructor: Error 3: Error 3: MyException: destructor: Error 3: Step 2: The lines get executed first. try { MyException error1("Error 1:") ; throw MyException("Error 2:") ; } catch ( MyException error2 ) { cout << error2.messg << endl ; } MyException error1("Error 1:") ; This creates the "error1" object and the statement: MyException: constructor: Error 1: Then we hit the "throw" statement: throw MyException("Error 2:") ; This will create an object and we get the below statement printed out: MyException: constructor: Error 2: We come to the handler but before doing that we need to clean up the local objects in the "try" block and the destructor for "error1" object get called. catch ( MyException error2 ) { cout << error2.messg << endl ; } The object with the message "Error 2" from the try block is copied to "error2" object. MyException: copy constructor: Error 2: We print out the message "Error 2:" and the come out of the handler. This causes the cleanup of the 2 "Error 2" objects: one that was thrown and the other one that was in the argument to the catch handler. We print out a debug statement and then hit the lines: try { throw MyException("Error 3:") ; } catch( MyException& error3 ) { cout << error3.messg << endl ; } Step 1: MyException: constructor: Error 3: Error 3: MyException: destructor: Error 3: Step 2: We create the "Error 3:" object in the "try" block and then jump to the handler. Since the argument "error3" is a reference we do not create another object and only 1 constructor is called as shown in the output.
Type Conversion
We need to be careful when stating the type of object in the "catch" handler. There is no implicit conversion performed. The exact type that was specified in the throw statement must be used.File: construct1.cpp
#include <iostream> #include <fstream> #include <string> using namespace std; int divideOperation2( int x1, int y1 ) { if ( y1 == 0 ) { cout << "Step1" << endl ; throw "Divide by zero." ; //throw String("Divide by zero.") ; } return ( x1 / y1 ) ; } void method1( string str1 ) { cout << "Inside method1: " << str1 << endl ; } int main() { try { string str1 = "Divide by zero." ; method1( str1 ) ; const char* str2 = "Divide by zero str2." ; method1( str2 ) ; string str3 = str2 ; divideOperation2 ( 4 , 0 ) ; } catch ( const string& str1 ) //catch ( const char* str1 ) { cout << "Step2" << endl ; cout << str1 << endl ; } cout << "Step3" << endl ; return 0; } $ g++ construct1.cpp ; ./a.exe Inside method1: Divide by zero. Inside method1: Divide by zero str2. Step1 The below code shows how a "const char*" can be implicitly converted to a string type. However the handler that contains a string object does not get called because the "const char*" is not implicitly converted to a string. string str1 = "Divide by zero." ; method1( str1 ) ; const char* str2 = "Divide by zero str2." ; method1( str2 ) ;
Inheritance
If we have a hierarchy then we can throw a derived exception and catch it with the base class.File: except8.cpp
#include <iostream> #include <fstream> using namespace std; class MyExceptionBase { public: string messg ; MyExceptionBase( string str1 ) { messg = str1 ; } }; class MyException : public MyExceptionBase { public: MyException( string str1 ) : MyExceptionBase( str1 ) { } }; int main() { try { throw MyException("Error") ; } catch ( MyExceptionBase& e1 ) { cout << e1.messg << endl ; } return 0; } $ g++ except8.cpp ; ./a.exe ErrorIf we list the classes in a hierarchy we need to make sure we list the derived classes first. The following produces a warning.
File: except9.cpp
#include <iostream> #include <fstream> using namespace std; class MyExceptionBase { public: string messg ; MyExceptionBase( string str1 ) { messg = str1 ; } }; class MyException : public MyExceptionBase { public: MyException( string str1 ) : MyExceptionBase( str1 ) { } }; int main() { try { throw MyException("Error") ; } catch ( MyExceptionBase& e1 ) { cout << e1.messg << endl ; } catch ( MyException& e1 ) { cout << "Derived" << e1.messg << endl ; } return 0; }
$ g++ except9.cpp ; ./a.exe except9.cpp: In function �int main()�: except9.cpp:36:5: warning: exception of type �MyException� will be caught by earlier handler [-Wexceptions] 36 | catch ( MyException& e1 ) | ^~~~~ except9.cpp:31:5: note: for type �MyExceptionBase� 31 | catch ( MyExceptionBase& e1 ) | ^~~~~ ErrorIt should be written as:
File: except10.cpp
#include <iostream> #include <fstream> using namespace std; class MyExceptionBase { public: string messg ; MyExceptionBase( string str1 ) { messg = str1 ; } }; class MyException : public MyExceptionBase { public: MyException( string str1 ) : MyExceptionBase( str1 ) { } }; int main() { try { throw MyException("Error") ; } catch ( MyException& e1 ) { cout << e1.messg << endl ; } catch ( MyExceptionBase& e1 ) { cout << e1.messg << endl ; } return 0; } $ g++ except10.cpp ; ./a.exe Error
Standard Exceptions
C++ provides standard exceptions that can be used to catch exceptions thrown by the language.
File: mem1.cpp
$ g++ mem1.cpp ; ./a.exe Standard exception: std::bad_allocWe can also derive our own class from the exception class.
File: mem2.cpp
Exception specifications allow us to specify if a method throws an exception or not. In the below code the "method3" is called but the exception is not caught even though there is a try catch block. This is because the "method3" threw an exception even though we declared that it does not do so.
File: except11.cpp