C++ Exception Handling



Preamble

Exception Handling (a.k.a. “exception management”) is a programming style that favors the implementation of a defense strategy when facing up abnormal behaviors, failures or, worst, stronger errors that may come from the Operating System (OS) itself (e.g., no more memory).

Promoted by Ada, “exception management” relies on a self-contained thread of control compared to the “normal” program execution. Exceptions are raised and caught depending upon the chosen defense strategy. In OO programming, exceptions are plain objects and, in C++, they benefit from being instantiated from a predefined (extensible) hierarchy of classes.

Headlines
Exception Handling

Rule(s)

Example (old school)

class Temperature {
    …                
public:
    Temperature() throw(); // Old style, no exception is raised (to be replaced by 'noexcept'!)
    …
    void decrement() throw(Invalid_temperature_exception); // Obsolete style from C++11
    …
};

Reinforcing the exception system with noexcept

Rule(s)

Example Exception_management.Cpp.zip 

void callee() noexcept(false); // Equivalent declaration: 'void callee();'
void caller() noexcept(true) {
    ::callee(); // No compilation error...
    // throw "Something while 'noexcept(true)'"; // Compilation error...
};

Rule(s)

Example Exception_management.Cpp.zip 

template<typename T> T my_function() noexcept(sizeof(T) < 4);
…
// Instantiation of 'my_function' with 'T' = 'void'
decltype(::my_function<void>()) *p; // Compilation error because 'sizeof(void)'

Creating new exception types

Rule(s)

Example Exception_management.Cpp.zip 

class Invalid_temperature_exception : std::exception { // '#include <stdexcept>'
    float _value; // In Celsius
public:
    Invalid_temperature_exception(float);
    float value() const;
    const char* what() const noexcept(true) final; // No exception can be thrown!
};

Declaring and throwing exceptions

Example Exception_management.Cpp.zip 

// '.h' file:
class Temperature {
public:
    enum class Temperature_unit : char /* 'char' -> integral type is required! */ {
        Celsius = 'C', Fahrenheit = 'F', Kelvin = 'K'
    };
    static const float Min;
private:
    float _value; // In Celsius
    float _step;
public:
    float asCelsius() const;
    float asFahrenheit() const;
    float asKelvin() const;
    void increment() noexcept(true);
    void decrement() noexcept(false);
    int operator<(const Temperature&) const;
    int operator<=(const Temperature&) const;
    int operator>(const Temperature&) const;
    int operator>=(const Temperature&) const;
    Temperature();
    Temperature(float, Temperature_unit = Temperature_unit::Celsius) noexcept(false);
};

// '.cpp' file:
void Temperature::decrement() noexcept(false) { // Potential throwing...
    _value -= _step;
    if (_value < Min) throw _value; // Effective throwing...
}
…
Temperature::Temperature(float value, enum Temperature_unit unit) noexcept(false) {
	switch (unit) {
        case Temperature_unit::Celsius: _value = value;
            break;
        case Temperature_unit::Fahrenheit: _value = (value - 32.F) * 5.F / 9.F;
            break;
        case Temperature_unit::Kelvin: _value = value + Min;
            break;
        default: throw "Illegal temperature unit";
    }
    if (_value < Min) throw Invalid_temperature_exception(_value);
    _step = 0.0001F;
}

Global management policy setup

Example Exception_management.Cpp.zip 

// '.h' file:
class Global_exception_management {
public:
    static void my_unexpected();
    static void my_terminate();
};

// '.cpp' file:
void Global_exception_management::my_unexpected() {
    std::cerr << "my_unexpected" << std::endl; // Partial exception processing
// Active exception is re-routed.
// This creates a chain so that an instance of 'std::bad_exception' is thrown whether the failing function has this type in its signature
    throw; 
}

void Global_exception_management::my_terminate() {
    std::cerr << "my_terminate" << std::endl; // For test only
}

std::set_unexpected(&Global_exception_management::my_unexpected);
std::set_terminate(&Global_exception_management::my_terminate);

Catching exceptions (through references for polymorphism)

Example Exception_management.Cpp.zip 

std::exception_ptr pe; // C++11
try {
// Instance of 'Invalid_temperature_exception' may potentially be raised (or "Illegal temperature unit"):
    Temperature t(0.F, Temperature::Temperature_unit::Kelvin);
    t.decrement(); // Value of 'float' *IS* raised...
} catch (const char* e) { // Matches 'throw "Illegal temperature unit"'
    std::cerr << "const char*: " << e << std::endl;
} catch (Invalid_temperature_exception& ite) { // Matches 'throw Invalid_temperature_exception(_value)'
    std::cerr << ite.what() << std::endl;
}    //catch (float& e) { // Matches 'throw _value' 
//	std::cerr << "float&: " << e << std::endl;
//}
catch (std::bad_exception& be) { // 'Global_exception_management::my_unexpected' is called before
    std::cerr << "be: " << be.what() << std::endl;
} catch (...) {
    std::cerr << "Default management" << std::endl;
    pe = std::current_exception(); // C++11
}
try {
    if (pe) std::rethrow_exception(pe);
}
// Great caution: missing 'throw _value':
catch (const std::exception& e) {
    std::cout << "Caught exception \"" << e.what() << "\"\n";
}