C++ PROGRAMMING ยท 17 MIN STUDY

Exception safety and resource-safe design

Apply the no-throw, strong, and basic exception-safety guarantees using RAII and transactional updates.

Learning objectives

  • Describe the basic, strong, and no-throw exception-safety guarantees.
  • Use RAII to prevent resource leaks during exceptions.
  • Design an update that either succeeds fully or leaves the original state unchanged.
  • Understand the role and limits of noexcept.

Prerequisites: exceptions, constructors and destructors, RAII, and std::vector. Review Exceptions and error handling and RAII and smart pointers first.

Why exception safety matters

An exception can interrupt a sequence of operations. If a function has already changed half of an object's state, callers may observe an inconsistent object. Exception safety describes what remains true when an operation fails.

Three common guarantees

GuaranteeMeaning
No-throwThe operation does not let exceptions escape.
StrongIf it fails, observable state is unchanged (transactional behavior).
BasicIf it fails, invariants hold and resources are not leaked, but state may have changed.

RAII protects resources

C++14 example: std::make_unique was added in C++14.

#include <memory>
void work() {
    auto data = std::make_unique<int[]>(100);
    // If an exception occurs here, data is still cleaned up.
}

When stack unwinding destroys the unique_ptr, its managed allocation is released. Prefer RAII wrappers over manual new/delete and manual cleanup paths.

Strong guarantee with copy-and-swap

#include <vector>

class Values {
    std::vector<int> data;
public:
    void replace(const std::vector<int>& incoming) {
        auto temporary = incoming; // may throw; this object unchanged
        data.swap(temporary);      // vector swap is non-throwing for this allocator setup
    }
};

The potentially failing copy happens before the object's state changes. If copying fails, the original vector remains intact. Once the copy succeeds, swap commits the replacement. In generic allocator-aware code, verify the swap's noexcept conditions rather than assuming every swap is non-throwing.

noexcept

void release_resource() noexcept {
    // cleanup that must not throw
}

noexcept is a contract: if an exception escapes a noexcept function, the program calls std::terminate. Do not add noexcept merely because a function is expected not to fail; ensure its operations really cannot throw or that termination is an intentional policy.

Common mistakes

  • Confusing the basic guarantee with no guarantee.
  • Assuming RAII prevents every logical partial update.
  • Marking a function noexcept while it calls potentially throwing operations without handling them.
  • Modifying object state before performing operations that may fail, when the strong guarantee is required.

Practice

  1. Which guarantee says the object's state is unchanged after failure?
  2. Why does unique_ptr help during stack unwinding?
  3. What happens if an exception escapes a noexcept function?
Show solutions
  1. The strong guarantee.
  2. Its destructor releases the resource automatically.
  3. std::terminate is called.

Summary

RAII protects resources; exception guarantees protect object invariants and caller expectations. Design strong-guarantee operations by preparing changes before committing them.

References