Unit 6: Handling Exceptions, Templates and STL

CAP455 — Object Oriented Programming Using C++ 2 min read

I. Orientation

Modern C++ supports reliable error handling, generic programming, and reusable data structures through three connected facilities: exceptions, templates, and the Standard Template Library. Exceptions separate error detection from recovery; templates allow one definition to operate on multiple types; and the STL supplies template-based containers, algorithms, and iterators.

  • Exception model: A throw expression reports an exceptional condition, and a compatible catch handler responds to it.
  • Generic programming: A template treats types or values as parameters, enabling compile-time reuse without sacrificing type safety.
  • STL organization: Containers store data, algorithms process ranges, and iterators connect the two.
  • Core convention: Most STL ranges are half-open, written conceptually as [first, last), where first is included and last marks one position beyond the range.
  • Resource safety: Automatic objects are destroyed during normal scope exit and exception propagation, supporting Resource Acquisition Is Initialization (RAII).
  • Required headers: Facilities are declared in headers such as <exception>, <stdexcept>, <vector>, <list>, and <algorithm>.

II. Exception Handling — Detecting and Recovering from Runtime Failures

A. Basics of exception handling

Exception handling transfers control from the point where a runtime problem is detected to a handler capable of responding to it.

  • Purpose: Exceptions represent conditions such as an invalid argument, unavailable resource, or out-of-range access without mixing recovery logic into every ordinary statement.
  • Three keywords:
    • try encloses code that may produce an exception.
    • throw creates or propagates an exception.
    • catch declares a handler for a matching exception type.
  • Standard exceptions: Classes in <stdexcept> include std::invalid_argument, std::out_of_range, and std::runtime_error.
  • Not ordinary control flow: Exceptions should report exceptional failures, not replace routine tests such as checking whether a menu choice equals 1.
  • Basic form:
CPP
try {
    if (denominator == 0)
        throw std::runtime_error("division by zero");
    result = numerator / denominator;
}
catch (const std::runtime_error& error) {
    std::cerr << error.what();
}
  • Concrete behavior: If denominator is 0, division is skipped and what() supplies the stored diagnostic message.

B. Exception handling mechanism

The exception handling mechanism searches dynamically for a type-compatible handler while unwinding active function calls.

  • Detection: A function first identifies a condition it cannot handle locally, such as failure to open a required file.
  • Propagation: After throw, execution does not continue at the next statement; the runtime examines surrounding handlers.
  • Stack unwinding: Automatic objects created after entry into each abandoned scope are destroyed in reverse construction order.
  • Handler search: If the current try statement has no match, the search continues through calling functions.
  • Termination: If no suitable handler exists, std::terminate() is invoked.
  • RAII connection: Objects such as std::vector and file-stream objects release their managed resources as their destructors run during unwinding.

C. Throwing exception mechanism

A throw expression supplies an exception object whose type determines which handler can catch it.

  • Syntax: throw expression; copies or moves the expression into an exception object managed by the runtime.
  • Preferred objects: Throw class objects, especially standard exception types, rather than numeric error codes or character pointers.
  • Validation example:
CPP
double squareRootInput(double value) {
    if (value < 0.0)
        throw std::domain_error("negative input");
    return value;
}
  • Concrete condition: Calling squareRootInput(-4.0) throws before returning a value.
  • Function guarantee: A function should leave program invariants intact before throwing; partially completed changes can otherwise corrupt state.
  • Construction failure: A constructor may throw when it cannot establish a valid object, in which case that object is not considered fully constructed.

D. Catching exception mechanism

A catch clause handles an exception when its declared parameter is compatible with the thrown object’s type.

  • Typed handler: catch (const std::exception& error) handles objects derived publicly from std::exception without copying or slicing them.
  • Ordering rule: Place handlers for derived exception classes before handlers for their base classes.
  • Multiple handlers:
CPP
try {
    process();
}
catch (const std::out_of_range& error) {
    std::cerr << error.what();
}
catch (const std::exception& error) {
    std::cerr << "General failure: " << error.what();
}
catch (...) {
    std::cerr << "Unknown failure";
}
  • Catch-all handler: catch (...) matches any exception but provides no direct access to its object.
  • Handler scope: After a handler finishes normally, execution continues after the complete trycatch sequence.

E. Re-throwing an exception

Re-throwing allows a handler to perform local work and then propagate the current exception to an outer handler.

  • Correct syntax: A bare throw; inside a handler preserves the original exception object and its dynamic type.
  • Typical purpose: A lower layer may log failure or restore local state while leaving policy decisions to a higher layer.
  • Example:
CPP
try {
    saveRecord();
}
catch (const std::exception& error) {
    log(error.what());
    throw;
}
  • Important contrast:
    1. throw; rethrows the currently handled object unchanged.
    2. throw error; throws a new copy whose static type may cause object slicing.
  • Restriction: Executing bare throw; when no exception is currently handled causes termination.

III. Templates — Compile-Time Generic Programming

A. Function template

A function template defines a family of functions by replacing a concrete type or value with a template parameter.

  • Declaration: template <typename T> introduces T as a type parameter; class may be used instead of typename in this context.
  • Example:
CPP
template <typename T>
T maximum(T left, T right) {
    return (left < right) ? right : left;
}
  • Instantiation: maximum(3, 7) generates an int specialization, while maximum(2.5, 1.5) generates a double specialization.
  • Type requirement: T must support the operations used in the definition; here, values of T must be comparable with <.
  • Deduction: The compiler usually infers T from arguments, though maximum<double>(3, 4.5) supplies it explicitly.
  • Overloading: Function templates may coexist with ordinary overloaded functions; normal overload resolution selects the best match.

B. Class template

A class template defines a family of classes whose data members or operations depend on one or more parameters.

  • Definition:
CPP
template <typename T>
class Box {
    T value;
public:
    explicit Box(const T& item) : value(item) {}
    const T& get() const { return value; }
};
  • Instantiation: Box<int> count(10); and Box<std::string> name("Ada"); are distinct class types.
  • Member definitions: A member defined outside the class must repeat the template declaration and use the qualified form Box<T>::member.
  • Non-type parameters: A template may accept a compile-time value, as in template <typename T, std::size_t N>.
  • Specialization: A full specialization provides custom behavior for one exact argument, while partial specialization customizes a category of arguments.
  • Compilation point: Template definitions are generally placed in header files because their complete definitions must be visible when instantiated.

C. Class template and inheritance

Class templates participate in inheritance as base classes, derived classes, or both, enabling reusable generic hierarchies.

  • Template-derived class:
CPP
template <typename T>
class PrintableBox : public Box<T> {
public:
    using Box<T>::Box;
    void print() const {
        std::cout << this->get();
    }
};
  • Dependent base rule: In a template derived from Box<T>, inherited members often require this->get() or Box<T>::get() because the base depends on T.
  • Concrete specialization as base: A normal class may inherit from Box<int>, which is a specific generated class type.
  • Polymorphism: Runtime polymorphism still requires virtual functions in the base; templates alone provide compile-time polymorphism.
  • Design limitation: Every specialization, such as Box<int> and Box<double>, is unrelated unless inheritance explicitly connects them.

IV. Standard Template Library — Reusable Containers and Operations

A. Introduction and importance of Standard Template Library (STL)

The STL is a major part of the C++ Standard Library that implements generic data storage and processing through templates.

  • Reuse: Standard components replace repeated implementations of dynamic arrays, linked lists, sorting, and searching.
  • Type safety: std::vector<int> accepts integers, and incorrect element use is generally diagnosed during compilation.
  • Efficiency: Algorithms and containers have documented complexity; for example, sorting n random-access elements takes approximately O(n log n) comparisons.
  • Interoperability: Iterator-based algorithms work with many containers without knowing their concrete representation.
  • Namespaces and headers: Components use the std namespace and must be included through their specified headers.
  • Generic principle: Programming against iterator capabilities makes code reusable across compatible data structures.

B. Containers

Containers are template classes that own and organize collections of objects.

  • Sequence containers: vector, list, and deque preserve an element sequence.
  • Associative containers: set and map maintain ordered keys, commonly giving O(log n) search.
  • Unordered containers: unordered_set and unordered_map use hashing and provide average O(1) lookup.
  • Container adaptors: stack, queue, and priority_queue expose restricted interfaces over underlying containers.
  • Common operations: size(), empty(), begin(), end(), and clear() occur across many container types.
  • Selection criterion: Choose by required access, insertion, deletion, ordering, and iterator-stability guarantees rather than syntax alone.

C. Algorithms

STL algorithms are generic functions that operate primarily on iterator ranges instead of directly owning data.

  • Examples: <algorithm> provides std::sort, std::find, std::count, std::reverse, and std::transform.
  • Range convention: std::sort(first, last) processes elements from first up to, but not including, last.
  • Concrete use:
CPP
std::vector<int> values{4, 1, 3, 2};
std::sort(values.begin(), values.end());
  • Result: values becomes {1, 2, 3, 4}.
  • Capability requirement: std::sort requires random-access iterators, so it works with vector but not directly with list.
  • Customization: Predicates and callable objects modify behavior, as in sorting integers with std::greater<int>().

D. Iterators

An iterator is an abstraction resembling a pointer that identifies a position in a container or range.

  • Core operations: Dereferencing with *it accesses an element, and incrementing with ++it advances the position.
  • Endpoints: begin() identifies the first element; end() is a past-the-end sentinel and must not be dereferenced.
  • Categories:
    • Input and output iterators support single-pass reading or writing.
    • Forward iterators support multipass forward traversal.
    • Bidirectional iterators also support decrement.
    • Random-access iterators support arithmetic such as it + n.
  • Traversal:
CPP
for (auto it = values.begin(); it != values.end(); ++it)
    std::cout << *it << ' ';
  • Invalidation: Container modifications may invalidate iterators; vector reallocation can invalidate all iterators, while list insertion normally preserves existing ones.

E. Vector container

std::vector<T> is a dynamic array that stores elements contiguously and grows automatically.

  • Access: operator[] provides unchecked indexed access; at(index) checks bounds and throws std::out_of_range.
  • Complexity: Indexing is O(1), insertion at the end is amortized O(1), and insertion near the beginning is O(n).
  • Capacity: size() counts elements, while capacity() reports allocated storage; reserve(n) can reduce reallocations.
  • Operations:
CPP
std::vector<int> numbers{10, 20};
numbers.push_back(30);
numbers.pop_back();
  • Result: After both operations, the vector again contains 10 and 20.
  • Best use: Vector is normally the default sequence container when contiguous storage and fast indexing are desired.

F. List container

std::list<T> is a doubly linked sequence optimized for insertion and deletion at known positions.

  • Structure: Each node stores an element and links to neighboring nodes, so elements are not contiguous.
  • Complexity: Insertion or erasure through a valid iterator is O(1), but locating the position remains O(n).
  • Traversal: List supplies bidirectional iterators and therefore does not support indexing or iterator arithmetic.
  • Operations:
CPP
std::list<int> values{10, 30};
auto position = std::next(values.begin());
values.insert(position, 20);
  • Result: The sequence becomes 10, 20, 30.
  • List-specific algorithm: Use values.sort() rather than std::sort, because list iterators are not random-access iterators.
  • Trade-off: Stable iterators and cheap node insertion cost extra link storage and generally poorer cache locality than vector.