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();
      
    }