Unit 6: Exception Handling, Templates and Standard Template Library

CSE202 — Object Oriented Programming 3 min read

I. Orientation

Object-oriented C++ programs use exception handling to manage runtime failures, templates to express type-independent code, and the Standard Template Library (STL) to provide reusable containers and operations. Together, these facilities improve program reliability, generality, and maintainability.

  • Exception handling: Separates error-detection code from error-recovery code through try, throw, and catch.
  • Templates: Define functions or classes using type parameters, allowing one definition to work with several compatible data types.
  • STL: Supplies standardized containers, iterators, algorithms, function objects, and related utilities.
  • Compile-time and runtime roles:
    • Templates are instantiated primarily at compile time.
    • Exceptions represent unusual conditions detected while a program is running.
    • STL algorithms are usually selected at compile time but operate on runtime data.
  • Core convention: Generic code must use only operations supported by the substituted type; exception handlers should catch types compatible with the thrown object.

II. Exception Handling — Detecting and Managing Runtime Failures

A. Basics of exception handling

Exception handling is a structured method of transferring control from the point where an abnormal condition is detected to code that can handle it.

  • Exception: An object or value representing an error, such as an invalid argument, failed allocation, or unavailable file.
  • Protected region: Statements that may fail are placed inside a try block.
  • Error signal: A throw expression creates or identifies an exception and begins control transfer.
  • Handler: A catch block specifies the exception type it can process.
  • Basic form:
CPP
try {
    // Statements that may throw
}
catch (const ExceptionType& error) {
    // Recovery or reporting
}
  • Advantage: Normal logic does not need repeated error-code checks after every operation.
  • Scope: Exceptions should represent exceptional failures, not ordinary decisions such as ending a loop or selecting a menu option.

B. Exception handling mechanism

The exception handling mechanism searches for a compatible handler after a throw expression is executed.

  • Execution sequence:
    1. Statements in the try block execute normally.
    2. An operation throws an exception.
    3. Remaining statements in that try block are skipped.
    4. The runtime searches outward for a matching catch.
    5. The selected handler executes, after which control continues beyond its handler sequence.
  • Handler matching: A handler normally matches the thrown type, a valid base-class type, or catch (...).
  • Stack unwinding: Automatic objects created after entering abandoned scopes are destroyed in reverse construction order.
  • RAII connection: Objects such as std::vector and std::fstream release their resources through destructors during unwinding.
  • Uncaught exception: If no matching handler exists, std::terminate() is called.
  • Standard hierarchy: Many library exceptions derive from std::exception, whose what() member returns an explanatory C-style string.

C. Throwing mechanism

The throwing mechanism uses a throw expression to report that an operation cannot complete normally.

  • Syntax:
CPP
throw expression;
  • Thrown object: C++ initializes an exception object from expression; throwing a descriptive class is preferable to throwing an unlabelled integer.
  • Standard example:
CPP
double divide(double a, double b) {
    if (b == 0.0)
        throw std::invalid_argument("division by zero");
    return a / b;
}
  • Meaning of symbols: a is the dividend, b is the divisor, and the function returns (a/b) only when b is nonzero.
  • Constructor validation: A constructor may throw when it cannot establish a valid object invariant.
  • Specification: A function declared noexcept promises not to let exceptions escape; violating that promise invokes std::terminate().
  • Good practice: Throw by value so the exception object has independent lifetime and can be matched predictably.

D. Catching mechanism

The catching mechanism selects a handler according to the type and order of the available catch clauses.

  • Typed handler:
CPP
try {
    std::cout << divide(10.0, 0.0);
}
catch (const std::invalid_argument& error) {
    std::cerr << error.what();
}
  • Reference binding: Catching as const Type& avoids copying and preserves polymorphic behavior.
  • Ordering rule: Handlers are tested in source order; a derived-class handler must precede its base-class handler.
  • Multiple handlers: One try block may distinguish std::out_of_range, std::bad_alloc, and other exception types.
  • Catch-all handler: catch (...) matches any exception but does not directly expose its type or value.
  • Handler scope: The parameter, such as error, exists only inside its catch block.
  • Completion: If the handler finishes normally, execution resumes after all handlers associated with that try, not at the throwing statement.

E. Rethrowing an exception

Rethrowing allows a handler to perform partial processing and then pass the current exception to an outer handler.

  • Syntax: A parameterless throw; inside a handler rethrows the currently handled exception.
CPP
void process() {
    try {
        throw std::runtime_error("processing failed");
    }
    catch (const std::exception& error) {
        std::cerr << "Local log: " << error.what() << '\n';
        throw;
    }
}
  • Purpose: A lower layer can log information or restore local state while allowing a higher layer to choose the recovery policy.
  • Type preservation: throw; preserves the original exception object and its dynamic type.
  • Contrast:
    1. throw; rethrows the current exception unchanged.
    2. throw error; throws a new copy based on the handler variable and may slice a derived exception to its base type.
  • Restriction: Executing parameterless throw; when no exception is currently being handled causes std::terminate().

III. Templates — Compile-Time Generic Programming

A. Function template

A function template defines a family of functions whose operations are expressed using one or more template parameters.

  • Declaration: template <typename T> introduces T as a type parameter.
CPP
template <typename T>
T maximum(T a, T b) {
    return (a > b) ? a : b;
}
  • Requirement: The substituted type must support operator> and copying or movement required by the return operation.
  • Instantiation: maximum(4, 9) causes deduction of T as int; maximum<double>(2.5, 1.8) specifies T explicitly.
  • Generated specialization: The compiler produces an appropriate function specialization when the template is used with a valid type.
  • Deduction limitation: maximum(3, 4.5) fails under this declaration because the arguments suggest different types for the single parameter T.
  • Non-type parameter: A template may also accept a compile-time value, as in template <typename T, std::size_t N>.
  • Benefit: One checked algorithm replaces separate int, double, and other overloads with identical logic.

B. Class template

A class template defines a family of classes in which member types or fixed values depend on template parameters.

  • Definition:
CPP
template <typename T>
class Box {
    T value;
public:
    explicit Box(const T& v) : value(v) {}
    const T& get() const { return value; }
};
  • Instantiation: Box<int> count(5); and Box<std::string> name("Ada"); are distinct class types.
  • Member meaning: In Box<T>, value has type T, while get() returns a constant reference to that stored value.
  • Type checking: Each instantiation is checked against the operations used by its members.
  • Out-of-class definition: A member defined outside the class must repeat the template declaration and qualified type:
CPP
template <typename T>
const T& Box<T>::get() const {
    return value;
}
  • Visibility rule: Template definitions are generally placed in header files because the compiler must see the complete definition when instantiating them.
  • Specialization: Explicit specialization can provide different behavior for a particular type when genuinely necessary.

C. Class template with inheritance

Class templates can participate in inheritance as template bases, template-derived classes, or derived classes with a fixed base specialization.

  • Generic inheritance:
CPP
template <typename T>
class Box {
protected:
    T value;
public:
    explicit Box(const T& v) : value(v) {}
};

template <typename T>
class LabeledBox : public Box<T> {
    std::string label;
public:
    LabeledBox(const T& v, const std::string& text)
        : Box<T>(v), label(text) {}
};
  • Base specialization: LabeledBox<int> inherits from Box<int>, while LabeledBox<double> inherits from Box<double>.
  • Initialization: Box<T>(v) invokes the appropriate base-class constructor before label is initialized.
  • Dependent base names: Members inherited from a template-dependent base may require qualification such as this->value.
  • Fixed inheritance: class IntegerBox : public Box<int> derives from one specific specialization rather than remaining generic.
  • Polymorphism: Runtime polymorphism still requires virtual functions; templating alone provides compile-time variation.
  • Design caution: A public inheritance relationship should model “is-a”; code reuse alone is often better served by composition.

IV. Standard Template Library — Reusable Data Structures and Operations

A. Introduction to STL containers, algorithms and iterators

The STL organizes generic programming around stored ranges, position-like iterators, and algorithms that operate on those ranges.

  • Containers: Objects that own and organize elements, including vector, list, deque, set, and map.
  • Algorithms: Generic functions such as std::sort, std::find, std::count, and std::reverse.
  • Iterators: Objects that identify positions and support traversal; begin() identifies the first element and end() the position one past the last.
  • Half-open range: Algorithms conventionally process [first, last), including first but excluding last.
CPP
auto position = std::find(values.begin(), values.end(), 7);
  • Separation of concerns: std::find does not need to know the container type; it depends on iterator operations and element comparison.
  • Iterator categories: Input, output, forward, bidirectional, and random-access iterators provide progressively different capabilities.
  • Header use: Containers and algorithms require headers such as <vector>, <list>, and <algorithm>.

B. Vector container

std::vector<T> is a dynamically sized sequence container that stores elements contiguously.

  • Declaration:
CPP
std::vector<int> values{4, 1, 7};
values.push_back(3);
std::sort(values.begin(), values.end());
  • Result: The vector becomes {1, 3, 4, 7} because vector iterators provide the random-access operations required by std::sort.
  • Access: values[i] performs unchecked indexing, whereas values.at(i) checks bounds and may throw std::out_of_range.
  • Complexity:
    • Indexed access is (O(1)).
    • push_back is amortized (O(1)).
    • Insertion or deletion near the front is (O(n)) because later elements move.
  • Capacity: size() reports stored elements, while capacity() reports currently allocated storage.
  • Invalidation: Reallocation can invalidate all pointers, references, and iterators referring to vector elements.
  • Best use: Vector is the usual default sequence container because contiguous storage provides efficient traversal and cache locality.

C. List container

std::list<T> is a doubly linked sequence container designed for stable insertion and erasure at known positions.

  • Structure: Each node stores an element and links to its previous and next nodes; elements are not contiguous.
  • Operations:
CPP
std::list<int> values{4, 1, 7};
auto position = std::find(values.begin(), values.end(), 1);
values.insert(position, 3);
values.sort();
  • Result: Insertion places 3 before 1, and the member sort() produces {1, 3, 4, 7}.
  • Complexity:
    • Insertion or erasure at a valid iterator is (O(1)).
    • Finding that position remains (O(n)).
    • Indexed access is unavailable.
  • Iterator capability: List iterators are bidirectional, so std::sort cannot operate on them; list::sort() is provided instead.
  • Stability: Insertion does not invalidate iterators or references to existing elements; erasure invalidates only those referring to erased elements.
  • Trade-off: Lists use extra memory for links and often traverse more slowly than vectors because nodes may be scattered in memory.