Skip to content
Theory · Exceptions · lesson 6 of 9

RAII and exception guarantees

While an exception flies down, every floor it passes closes, and the lamps of its local objects switch off by themselves: destructors run during stack unwinding. But an object from new has no such switch. That lamp is switched off only by hand, with delete, and execution may never reach that line.

An exception is one more way out of a function, and resources must be released on that path too. Stack unwinding runs the destructors of local objects, but a plain pointer has no destructor that would delete its object:

C++
#include <iostream>
#include <memory>
#include <stdexcept>

struct Noisy { ~Noisy() { std::cout << "~Noisy "; } };
int main() {
    try {
        Noisy* raw = new Noisy;  // ✗
        auto owned = std::make_unique<Noisy>();
        throw std::runtime_error("fail");
        delete raw;
    } catch (const std::exception& e) {
        std::cout << e.what() << '\n';  // ~Noisy fail
    }
}

Let's see why the destructor ran once, although there are two Noisy objects:

  1. raw gets the address of the first Noisy on the heap.
  2. owned owns the second Noisy through a std::unique_ptr.
  3. throw cuts the try block short, and execution never reaches delete raw.
  4. During stack unwinding owned is destroyed and deletes its object: ~Noisy is printed.
  5. The first Noisy stays on the heap—a leak.

An owner on every path

So that an exception leaves no leaks, resources are handed to RAII owners such as std::unique_ptr instead of wrapping each delete in a try block. An owner's destructor runs on every path out of a function, an exception included, as long as some handler catches it.

Three levels of guarantees

In case of an exception, a function can give one of three guarantees. Interviewers often ask about them, so let's go through each one.

Basic guarantee

Basic guarantee: nothing leaks, and all objects stay usable, although their values may have changed. RAII owners give the first part of the basic guarantee: an exception causes no leaks.

Strong guarantee

Strong guarantee: if the call ends with an exception, the program's state is the same as before the call. It is usually achieved like this: the new value is built in a local variable and handed out only at the end.

C++
void add_pair(std::vector<int>& v, int a, int b) {
    std::vector<int> copy = v;
    copy.push_back(a);
    copy.push_back(b);
    v = std::move(copy);
}

Line by line:

  1. copy is a copy of the vector v, and from here on the function changes only the copy.
  2. Any push_back may throw an exception if memory runs out. Then stack unwinding destroys copy, and v stays as it was.
  3. v = std::move(copy) moves the finished result into v and throws no exceptions.

If the function added the elements straight into v, an exception in the second push_back would leave only a there. That is the basic guarantee, but not the strong one.

No-throw guarantee

No-throw guarantee: the function never throws an exception at all. This promise is written in the function's declaration with the word noexcept.

Key points

  • An exception is one more way out of a function, and only a destructor releases resources on that path.
  • An object from new without an owner leaks if an exception skips delete; an RAII owner releases it by itself.
  • Basic guarantee: no leaks, objects stay usable; strong: after an exception the state is unchanged.
  • The strong guarantee comes from building the result in a copy and handing it out at the end.

Subscribe to take the course

The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.

Sign in another way