Beginner's Guide
    First Steps
    Input & Output
    Custom Types – Part 1
    Diagnostics
    Standard Library – Part 1
    Function Objects
    Standard Library – Part 2
    Code Organization
    Custom Types – Part 2
    Generic Programming
    Memory Management
    Software Design Basics

    Exceptions Exceptions Exceptions

    Intro

    • throwing transfers control back to the caller of the current function
    • they can be caught / handled via try … catch blocks
    • if not handled, exceptions propagate up until they reach main
    • if an exception is not handled in mainstd::terminate will be called
    • default behavior of std::terminate is to abort the program

    example that shows how exceptions propagate upwards the call chain

    First Example

    was to report the failure of a constructor to properly initialize an object, i.e., failure to establish the required class invariants (a constructor does not have a return type that could be used for error reporting)

    #include <stdexcept> // standard exception types class Fraction { int numer_; int denom_; public: explicit constexpr Fraction (int numerator, int denominator): numer_{numerator}, denom_{denominator} { if (denom_ == 0) throw std::invalid_argument{ "denominator must not be zero"}; }  }; 
    int main () { try { int d = 1; std::cin >> d; Fraction f {1,d}; } catch (std::invalid_argument const& e) { // deal with / report error here std::cerr << "error: " << e.what() << '\n'; } }

    Usages: Report Contract Violations

    • Precondition = expectation regarding inputs (valid function arguments)
    • Violation Examples: out-of-bounds container index / negative number for square root
    • Wide Contract Functions perform precondition checks before using their input values

      These are usually not used in performance-critical code where one does not want to pay the cost of input validity checks if passed-in arguments are already known to be valid.

    • Public member function fails to set valid member values
    • Example: out of memory during vector growth
    • Postcondition = expectation regarding outputs (return values)
    • Violation = function fails to produce valid return value or corrupts global state
    • Examples:
      • constructor fails
      • can't return result of division by zero
    • separation of error handling code from business logic
    • centralization of error handling (higher up the call chain)
    • nowadays negligible performance impact when no exception is thrown
    • but, usually performance impact when exception is thrown
    • performance impact due to extra validity checks
    • easy to produce resource/memory leaks (more below)

    Alternatives to Exceptions

    • narrow contract functions: make sure arguments are valid before passing them
    • use parameter types that preclude invalid values
    • this is preferred nowadays for better performance
    • error states / flags
    • set object to special, invalid value / state
    • return error code via separate output parameter (reference or pointer)
    • return special, invalid value
    • use special vocabulary type that can either contain a valid result or nothing, like C++17's std::optional or Haskell's Maybe

    Standard Library Exceptions

    Exceptions are one of the few places where the C++ standard library uses inheritance:
    All standard exception types are subtypes of std::exception.

    Some standard library containers offer wide contract functions that report invalid input values by throwing exceptions:

    std::vector<int> v {0,1,2,3,4}; // narrow contract:// no checks, max performance int a = v[6]; //  UNDEFINED BEHAVIOR // wide contract:// checks if out of bounds int b = v.at(6); // throws std::out_of_range

    Handling

    Centralize Exception Handling!

    • avoids code duplication if same exception types are thrown in many different places
    • useful for converting exceptions into error codes
    void handle_init_errors () { try { throw; // re-throw! } catch (err::device_unreachable const& e) {  } catch (err::bad_connection const& e) {  } catch (err::bad_protocol const& e) {  } } void initialize_server () { try {  } catch (...) { handle_init_errors(); } } void initialize_clients () { try {  } catch (...) { handle_init_errors(); } }

    Problems & Guarantees

    Resource Leaks

    ⇒ heavy impact on design of C++ types and libraries

    • external C libraries that do their own memory management
    • (poorly designed) C++ libraries that dont't use RAII for automatic resource management
    • (poorly designed) types that don't clean up their resources on destruction

    i.e., two separate functions for resource initialization (connect) and finalization (disconnect)

    void add_to_database (database const& db, std::string_view filename) { DBHandle h = open_dabase_conncection(db);  auto f = open_file(filename); // if 'open_file' throws ⇒ connection not closed!  // do work… close_database_connection(h); // ↑ not reached if 'open_file' threw }

    Use RAII To Prevent Leaks! RAII Prevents Leaks! RAII

    • constructor: resource acquisition
    • destructor: resource release/finalization
    • objects in local scope destroyed: destructors called
    • with RAII: resources properly released/finalized
    class DBConnector { DBHandle handle_; public: explicit DBConnector (Database& db): handle_{make_database_connection(db)} {}  ~DBConnector () {  close_database_connection(handle_);  }  // make connector non-copyable: DBConnector (DBConnector const&) = delete; DBConnector& operator = (DBConnector const&) = delete; }; 
    void add_to_database (database const& db, std::string_view filename) { DBConnector(db); auto f = open_file(filename); // if 'open_file' throws ⇒ connection closed! // do work normally… } // connection closed!

    Write an RAII wrapper if you have to use a library (e.g., from C) that employs separate functions for initilization and finalization of resources.

    Often, it also makes sense to make your wrapper non-copyable (delete the copy constructor and copy assignment operator), especially if one has no control over the referenced external resources.

    Destructors: Don't Let Exceptions Escape!: No Exceptions!

    class MyType { public: ~MyType () {  try { // y throwing code… } catch ( /*  */ ) { // handle exceptions… }  } };

    Exception Guarantees

    in case an exception is thrown:

    must be assumed of any C++ code unless its documentation says otherwise:

    • operations may fail
    • resources may be leaked
    • invariants may be violated (= members may contain invalid values)
    • partial execution of failed operations may cause side effects (e.g. output)
    • exceptions may propagate outwards
    • invariants are preserved, no resources are leaked
    • all members will contain valid values
    • partial execution of failed operations may cause side effects (e.g., values might have been written to file)

    This is the least you should aim for!

    • operations can fail, but will have no observable side effects
    • all members retain their original values

    Memory-allocating containers should provide this guarantee, i.e., containers should remain valid and unchanged if memory allocation during growth fails.

    • operations are guaranteed to succeed
    • exceptions not observable from outside (either none thrown or caught internally)
    • documented and enforced with noexcept keyword

    Prefer this in high performance code and on resource constrained devices.

    No-Throw Guarantee: noexcept noexcept noexcept C++11

    void foo () noexcept { … }
    • 'foo' promises to never throw exceptions / let any escape
    • if an exception escapes from a noexcept function anyway ⇒ program will be terminated
    • noexcept is part of a function's interface (even part of a function's type as of C++17)
    • changing noexcept functions back into throwing ones later might break calling code that relies on not having to handle exceptions

    Conditional noexcept Conditional

    constexpr int N = 5; // 'foo' is noexcept if N < 9 void foo () noexcept( N < 9 ) { … } // 'bar' is noexcept if foo is void bar () noexcept( noexcept(foo()) ) {  foo();  }

    noexcept(true) by Default Default

    • default constructors
    • destructors
    • copy constructors, move constructors
    • copy-assignment operators, move-assignment operators
    • inherited constructors
    • user-defined destructors
    • they are required to call a function that is noexcept(false)
    • an explicit declaration says otherwise

    More More

    Termination Handler Terminate

    • std::terminate is called
    • which calls the termination handler
    • which by default calls std::abort and thereby terminates the program normally

    std::set_terminate(handler); sets the function(object) that is called by std::terminate

    #include <stdexcept> #include <iostream> void my_handler () { std::cerr << "Unhandled Exception!\n"; std::abort(); // terminate program } int main () { std::set_terminate(my_handler);  throw std::exception{};  }
     $ g++ main.cpp -o test $ ./test Unhandled Exception!

    Exception Pointers exception_ptr

    • captures the current exception object
    • returns a std::exception_ptr referring to that exception
    • if there's no exception ⇒ an empty std::exception_ptr is returned
    • either holds a copy or a reference to an exception
    • throws an exception object referred to by an exception pointer
    #include <exception> #include <stdexcept> void handle_init_errors ( std::exception_ptr eptr) { try { if (eptr) std::rethrow_exception(eptr); } catch (err::bad_connection const& e) {  } catch (err::bad_protocol const& e) {  } } void initialize_client () { if () throw err::bad_connection;   } int main () { std::exception_ptr eptr; try { initialize_client();   } catch (...) { eptr = std::current_exception(); } handle(eptr); } // eptr destroyed // ⇒ captured exceptions destroyed

    Counting Uncaught Exceptions uncaught_exceptions C++17

    returns the number of currently unhandled exceptions in the current thread

    #include <exception> void foo () { bar(); // might have thrown int count = std::uncaught_exceptions();  }