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.
noexcept
Rule(s)
- C++11 comes with a renewed way of dealing with exceptions including the
noexcept
keyword and new types in the exception hierarchy like, for instance,std::system_error
. Note thatstd::bad_exception
is raised when there is no matching at catching time.- Historically, exception management in C++ fits genericity with difficulty: if
T
is a formal parameter type in a generic class then no hypothesis can be made about exceptions associated with functions offered byT
. C++11 goes beyond…- Contrary to Java, C++ assertions are not associated with equivalent exception types. C++ assertions are only macro-statements that are expanded at compilation time in “DEBUG” versions.
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)
- The list the exceptions that a function might directly or indirectly throw, even specifiable, is not (contrary to Java for instance) part of the function's signature (such a list is no longer supported in C++20). In this context, the
noexcept
keyword aims at replacingthrow()
along with checking at compilation time.- A
noexcept
function may even throw an exception by calling a third-party function, which potentially throws something.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)
noexcept
may be used with a generic function and, from C++17,noexcept
is part of the function's signature (type).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)
- User-defined exception types may benefit from having polymorphic functions like
what
. Differently,final
may be used when one desires thatwhat
cannot be overridden.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"; }