code/misc/exceptions.cpp

                

                

A study of some aspects of C++ error handling, focused on exceptions with a sprinkling of assertions and error code returns.

                
#define NDEBUG 1

#include <exception>
#include <iostream>
#include <vector>
#include <string>
#include <cassert>
#include <cstdlib>
using namespace std;

                

We should always give our constants symbolic names, so we can change them in one place: don't repeat yourself!

                
const int MAX_TEMP = 90;  // can't set thermostat higher
                

TempTooHigh: an exception to throw when we get a temperature out of any reasonable range. Its constructor collects some relevant information about the problem that occurred that can be shared with the user.

                
class TempTooHigh : public exception {
public:
    TempTooHigh(int n, int line, string file) 
        : too_high(n), line_num(line), file_name(file)
    {
    }

    const char* what() const throw() {
        return " is too high a temperature";
    }
    int high_temp() const { return too_high; }
    int which_line() const { return line_num; }
    string which_file() const { return file_name; }

private:
    int too_high;
    int line_num;
    string file_name;
};

                

set_thermostat() throws TempTooHigh when n > MAX_TEMP.

                
void set_thermostat(int n) {
    if (n > MAX_TEMP) throw(TempTooHigh(n, 57, "exceptions.cpp"));
    throw(exception());
}

                

set_temp() calls set_thermostat(). The only purpose of this intermediate call is to show how exceptions rise up the stack.

                
void set_temp(int n) {
    set_thermostat(n);
}

                

This function will throw an exception because vector.at() does range checking.

                
void h(int n) {
    vector<int> v = vector<int>(n, -1);
//    cout << v[n] << endl;
    cout << v.at(n) << endl;
}

                

The get_records() call returns error codes, for comparison.

                
int get_records() {
                

We imagine here we make some database access, that returns a number of records, or an error code.

                
    return rand();
}

                

main() will call some functions that throw exceptions, and use an assertion.

                
int main() {
    int n = MAX_TEMP + 1;
                

We illustrate how assert(cond) is used.

                
    assert(n <= MAX_TEMP);
                

The following is the sort of code that we get when we have error code returns.

                
    int ret_code = get_records();
    if (ret_code == -1)
        cout << "database not available\n";
    else if (ret_code == -2)
        cout << "No records present\n";
    else
        cout << "Proceeding to process records\n";

    try {
        set_temp(n);
    }
    catch (TempTooHigh& too_high) {
        cerr << "Temperature error: "
            << too_high.high_temp() << too_high.what()
            << " at line " << too_high.which_line()
            << " in file " << too_high.which_file() << endl;
    }
    catch (exception& e) {
        cerr << "Caught exception: " << e.what() << endl;
    }
                

Next we will illustrate catching any exception that inherits from std::exception.

                
    try {
        h(80);
    }
    catch (out_of_range& oor) {
        cerr << "Caught out of range: " << oor.what() << endl;
    }
    catch (exception& e) {
        cerr << "Caught exception: " << e.what() << endl;
    }

                

The point of the output that follows is to demonstrate that our exceptions, when caught, do not terminate the program.

                
    cout << "Still in main()!\n";
}