Skip to content

Chapter 8 of 8

Error Handling, Concurrency, and the Compilation Model

C++ handles errors through exceptions and the try-catch construct. A try block contains code that may throw, and catch blocks match against thrown exception types; exceptions propagate up the call stack until a matching handler is found. The recommended pattern is to throw by value but catch by const reference, as in catch (const std::exception& e), which avoids object slicing and unnecessary copies while still letting the handler examine the polymorphic object. std::exception is the base of the standard exception hierarchy; important derived classes include std::runtime_error for runtime problems, std::logic_error for logical mistakes, std::bad_alloc for failed memory allocation, and std::out_of_range for index errors. The noexcept specifier declares that a function will not throw, which permits the compiler to optimize and is particularly important for move operations, destructors, and swap; if a noexcept function does throw, std::terminate is called. std::terminate is also invoked when an exception escapes a noexcept function during stack unwinding, when an exception is thrown during stack unwinding, or when no catch handler matches a propagating exception. Stack unwinding is the process of destroying automatic objects in each frame as the exception travels upward, which is what makes RAII cleanup reliable. A function that may throw can declare so explicitly with noexcept(false), but this is rarely needed. Functions offer four levels of exception safety: nothrow (never throws), strong (either the operation completes or the state is unchanged), basic (no leaks and the object remains usable), and none (anything may happen). All of these guarantees are checked at runtime, in contrast to compile-time checks such as template instantiation, type checks, and constant folding that the compiler performs while translating source code into object code.

Concurrency in C++ is built around threads, tasks, and synchronization. std::thread launches a new OS thread running a callable, and every std::thread must be joined or detached before destruction or the program terminates. std::async runs a callable asynchronously and returns a std::future whose get() retrieves the result or rethrows an exception. std::promise lets a producer set a value or exception, while the associated std::future lets a consumer retrieve it, providing a one-shot channel between threads. std::mutex provides mutual exclusion, but locking and unlocking by hand is error-prone, so the standard library offers RAII wrappers: std::lock_guard is the lightweight option with no extra features, while std::unique_lock additionally supports deferred locking, manual unlock, and ownership transfer. std::scoped_lock in C++17 can lock several mutexes at once with a deadlock-avoidance algorithm. A std::condition_variable allows threads to wait until a predicate is satisfied, enabling blocking queues and similar patterns. A deadlock occurs when two or more threads each hold a lock and wait for a lock held by the other, and is prevented by acquiring locks in a fixed order or by using std::scoped_lock. A race condition occurs when program behavior depends on the non-deterministic timing of concurrent accesses to shared data, producing different results on each run; the remedies are synchronization through mutexes or atomic operations. std::atomic in <atomic> provides lock-free thread-safe access to a value with operations such as fetch_add, and supports explicit memory ordering.

The compilation model in C++ centers on translation units. A translation unit is the result of preprocessing a source file together with the headers it includes. The preprocessor runs before compilation and handles #include to insert a file, #define to define a textual macro, #ifdef and #ifndef for conditional compilation, and #pragma for implementation-defined hints. The text-substitution nature of #define means it has no type or scope and can introduce subtle bugs, so typed, scoped const or constexpr constants are preferred. Headers are typically protected by include guards or #pragma once so that repeated inclusion in a single translation unit does not cause multiple-definition errors. The One Definition Rule requires exactly one definition of each non-inline function or variable across the entire program, while inline functions and class definitions may appear in many translation units identically. A declaration introduces a name and its type, such as extern int x, while a definition provides storage or a function body, as in int x = 5; definitions are also declarations, but declarations may appear many times. Two kinds of assertions help verify invariants: the runtime assert(expr) from <cassert> aborts the program when the condition is false and is disabled when NDEBUG is defined, while the compile-time static_assert(cond, message) produces a compilation error if the condition fails. Undefined behavior covers anything the standard does not define, including signed integer overflow, null-pointer dereference, reading uninitialized memory, and out-of-bounds access; the compiler may assume such situations never occur and optimize accordingly. Implementation-defined behavior, by contrast, must be documented by the implementation, and examples include the size of int, byte order, and the value of a char after overflow.

All chapters
  1. 1Object-Oriented Foundations in C++
  2. 2Inheritance and Polymorphism
  3. 3Object Lifecycle and Resource Management
  4. 4Templates and Generic Programming
  5. 5Standard Library Containers and Iterators
  6. 6Modern C++ Utilities and Language Features
  7. 7Memory Management, Pointers, and Casting
  8. 8Error Handling, Concurrency, and the Compilation Model

Drill it

Reading is not remembering. These come from the Cpp Programming deck:

Q

What are the four main principles of OOP in C++?

The four pillars of OOP are:Encapsulation – bundling data and methods togetherAbstraction – hiding complex implementation detailsInheritance – deriving new clas...

Q

What is a class in C++?

A class is a user-defined type that encapsulates data members and member functions. Defined with the class keyword:class MyClass { public: int x; void show(); }...

Q

What is the difference between a class and a struct in C++?

The only default difference is access control:class members are private by defaultstruct members are public by defaultBoth can have methods, constructors, and i...

Q

What is inheritance in C++?

Inheritance allows a derived class to inherit members from a base class:class Dog : public Animal { };This promotes code reuse and establishes an is-a relations...