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

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

    #include <stdexcept>  // standard exceptions 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 valid function arguments
    • Violation Example: out-of-bounds container index
    • 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
    • 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 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 ExceptionHandling!

    • 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 {
          // potentiall throwing code…
        } catch ( /*  */ ) {
          // handle exceptions…
        } 
      }
    };

    Exception Guarantees Guarantees

    must be assumed of any C++ code unless its documentation doesn't say 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 constraint 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)
    • the 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();
      
    }