Introduction To Exceptions

Introduction

Exception handling is a language construct for handling the occurrence of exceptions, special conditions that change the normal flow of program execution.

When return codes are not enough

  1. Return codes can be cryptic: It's often hard to understand what a return value is.
  2. Functions can only return one code.
  3. In sequences of code where many things can go wrong, error codes have to be checked constantly.
  4. Constructors cannot return codes
  5. When an error code is returned to the caller, the caller may not always be quipped to handle the error.

Throwing Exceptions

  • A throw statement is used to signal that an exception or error case has occurred. It is also known as raising an exception.

Looking and Handling for Exceptions

  • In C++, we use the try keyword to define a block of statements.
  • The catch keyword is used to define a block of code.

Example

int main()
{
    try
    {
        // Statements that may throw exceptions you want to handle now go here
        throw -1;
    }
    catch (int)
    {
        // Any exceptions of type int thrown within the above try block get sent here
        cerr << "We caught an exception of type int" << endl;
    }
    catch (double)
    {
        // Any exceptions of type double thrown within the above try block get sent here
        cerr << "We caught an exception of type double" << endl;
    }
 
    return 0;
}

Examples

Example 1: How exceptions are handled immediately.

int main()
{
    try
    {
        throw 4.5; // throw exception of type double
        cout << "This never prints" << endl;
    }
    catch(double dX) // handle exception of type double
    {
        cerr << "We caught a double of value: " << dX << endl;
    }
}

Example 2: Calculating the square root of a number.

#include "math.h" // for sqrt() function
using namespace std;
 
int main()
{
    cout << "Enter a number: ";
    double dX;
    cin >> dX;
 
    try // Look for exceptions that occur within try block and route to attached catch block(s)
    {
        // If the user entered a negative number, this is an error condition
        if (dX < 0.0)
            throw "Can not take sqrt of negative number"; // throw exception of type char*
 
        // Otherwise, print the answer
        cout << "The sqrt of " << dX << " is " << sqrt(dX) << endl;
    }
    catch (char* strException) // catch exceptions of type char*
    {
        cerr << "Error: " << strException << endl;
    }
}

Example 3: Multiple statements within a try block

Instead of doing this…

std::ifstream fSetupIni("setup.ini"); // open setup.ini for reading
if (!fSetupIni)
    return ERROR_OPENING_FILE; // Some enum value indicating error
 
// Note that error handling and actual code logic are intermingled
 
if (!ReadParameter(fSetupIni, m_nFirstParameter))
    return ERROR_PARAMETER_MISSING; // Some other enum value indicating error
if (!ReadParameter(fSetupIni, m_nSecondParameter))
    return ERROR_PARAMETER_MISSING; // Some other enum value indicating error
if (!ReadParameter(fSetupIni, m_nThirdParameter))
    return ERROR_PARAMETER_MISSING; // Some other enum value indicating error

It's better to use exceptions.

std::ifstream fSetupIni("setup.ini"); // open setup.ini for reading
if (!fSetupIni)
    return ERROR_OPENING_FILE; // Some enum value indicating error
 
// Read parameters and return an error if the parameter is missing
try
{
    // Here's all the normal code logic
    m_nFirstParameter = ReadParameter(fSetupIni);
    m_nSecondParameter = ReadParameter(fSetupIni);
    m_nThirdParameter = ReadParameter(fSetupIni);
}
catch (int) // Here's the error handling, nicely separated
{
    return ERROR_PARAMETER_MISSING; // Some other enum value indicating error
}

**Example 4: Rewriting the square root program.

#include "math.h" // for sqrt() function
using namespace std;
 
// A modular square root function
double MySqrt(double dX)
{
    // If the user entered a negative number, this is an error condition
    if (dX < 0.0)
        throw "Can not take sqrt of negative number"; // throw exception of type char*
 
    return sqrt(dX);
}
 
int main()
{
    cout << "Enter a number: ";
    double dX;
    cin >> dX;
 
    try // Look for exceptions that occur within try block and route to attached catch block(s)
    {
        cout << "The sqrt of " << dX << " is " << MySqrt(dX) << endl;
    }
    catch (char* strException) // catch exceptions of type char*
    {
        cerr << "Error: " << strException << endl;
    }
}

Example 5: Demonstrating stack unwinding

#include <iostream>
using namespace std;
 
void Last() // called by Third()
{
    cout << "Start Last" << endl;
    cout << "Last throwing int exception" << endl;
    throw -1;
    cout << "End Last" << endl;
 
}
 
void Third() // called by Second()
{
    cout << "Start Third" << endl;
    Last();
    cout << "End Third" << endl;
}
 
void Second() // called by First()
{
    cout << "Start Second" << endl;
    try
    {
        Third();
    }
    catch(double)
    {
         cerr << "Second caught double exception" << endl;
    }
    cout << "End Second" << endl;
}
 
void First() // called by main()
{
    cout << "Start First" << endl;
    try
    {
        Second();
    }
    catch (int)
    {
         cerr << "First caught int exception" << endl;
    }
    catch (double)
    {
         cerr << "First caught double exception" << endl;
    }
    cout << "End First" << endl;
}
 
int main()
{
    cout << "Start main" << endl;
    try
    {
        First();
    }
    catch (int)
    {
         cerr << "main caught int exception" << endl;
    }
    cout << "End main" << endl;
 
    return 0;
}
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-Share Alike 2.5 License.