Skip to Content
Course content

100: noexcept and Its Guarantees

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're hiring a contractor to renovate your kitchen. Normally, contractors leave a bit of wiggle room in the contract: "I'll do the job, but if I find mold in the walls or a burst pipe, I'm going to stop work and call you to figure out a new plan." That's how most C++ functions work—they might finish the job, or they might throw an exception that you (the caller) have to handle.

Now, imagine a "Zero-Surprise Guarantee" contract. The contractor promises that under no circumstances will they stop work or ask for more money. They guarantee the job will be completed exactly as described. But there's a catch: if they accidentally break a pipe despite that guarantee, they don't call you to negotiate. They just walk off the job and the whole project is immediately declared a total loss. No recovery, no fixing it—just total failure.

In C++, noexcept is that "Zero-Surprise Guarantee." When you mark a function as noexcept, you aren't just telling the programmer "I don't plan to throw anything"; you are telling the compiler, "If an exception ever escapes this function, just kill the whole program immediately."

The Promise You Can't Break

When you add noexcept to a function signature, you're creating a hard contract. If the function throws an exception that isn't caught inside the function body, the runtime calls std::terminate(). It doesn't look for a catch block further up the stack. It just ends.

struct DataBuffer {
    int* data;
    
    // I'm promising this will never throw.
    ~DataBuffer() noexcept { 
        delete[] data; 
    }

    void clear() noexcept {
        // If I accidentally called something here that threw an exception,
        // the program would crash instantly.
        data = nullptr;
    }
};

I'll be honest: beginners often think, "Why would I want my program to crash instead of letting me catch the error?" The answer isn't about error handling—it's about performance and the "Strong Exception Guarantee."

Why the Standard Library Cares

This is where noexcept actually becomes a tool for speed. Let's look at std::vector. When a vector needs to grow, it allocates a new, larger block of memory and moves the existing elements from the old block to the new one.

Here is the dilemma the vector faces: if it's moving 100 elements and the 50th element's move constructor throws an exception, the vector is in a nightmare state. Half the elements are moved, half are still in the old spot, and the original state is destroyed. It can't easily "roll back" the move.

To solve this, std::vector checks if your move constructor is marked noexcept. If it is, the vector uses the fast move. If it isn't, the vector plays it safe and copies every single element instead. If a copy throws, the vector can just discard the new memory and the original data remains untouched. I've seen developers wonder why their "efficient" move constructors weren't being called, only to realize they forgot that one little keyword.

class HeavyObject {
    std::string name;
    int* large_array;

public:
    // Without noexcept, std::vector will COPY this object during resize
    HeavyObject(HeavyObject&& other) noexcept {
        this->large_array = other.large_array;
        other.large_array = nullptr;
    }
};

Conditional Guarantees

Sometimes, you don't know if a function will throw until you know what's happening inside a template. C++ gives us a way to make the noexcept specifier conditional. You can pass a boolean expression into noexcept().

If the expression inside the parentheses evaluates to true, the function is treated as noexcept. If false, it's treated as a normal, potentially-throwing function.

template <typename T>
void swap_values(T& a, T& b) noexcept(noexcept(a.swap(b))) {
    a.swap(b);
}

Read that carefully: noexcept(noexcept(a.swap(b))). The inner noexcept(...) is actually an operator that asks the compiler, "Would this expression throw?" The outer noexcept then applies that answer to the function itself. It's a bit meta, but it's the only way to write generic library code that remains performant across different types.




📋 Practical Task

Exercise: Optimizing a Custom Buffer for Vector Resizing

You have a class called SmartBuffer that manages a heap-allocated array. Currently, when this class is stored in a std::vector, the vector is performing expensive copies during resizing because it doesn't trust the move constructor.

Your Task:

  1. Implement a move constructor for SmartBuffer that transfers ownership of the pointer.
  2. Apply the correct noexcept specifier to ensure std::vector utilizes the move constructor instead of falling back to the copy constructor.
  3. Implement a move assignment operator, also marked noexcept.
#include <iostream>
#include <vector>
#include <string>

class SmartBuffer {
    size_t size;
    int* buffer;

public:
    SmartBuffer(size_t s) : size(s), buffer(new int[s]) {
        std::cout < "Allocating buffer of size " < size < "\n";
    }

    ~SmartBuffer() {
        delete[] buffer;
    }

    // Copy constructor (keep this as is)
    SmartBuffer(const SmartBuffer& other) : size(other.size), buffer(new int[other.size]) {
        std::cout < "Copying buffer...\n";
        for (size_t i = 0; i < size; ++i) buffer[i] = other.buffer[i];
    }

    // TODO: Implement the move constructor here. 
    // Make sure it is marked so std::vector will use it!

    // TODO: Implement the move assignment operator here.
    // Make sure it is marked so std::vector will use it!
};

int main() {
    std::vector<SmartBuffer> vec;
    vec.reserve(1);
    vec.emplace_back(10); // First element
    
    std::cout < "Triggering resize...\n";
    vec.emplace_back(20); // This should trigger a resize and move the first element
    
    return 0;
}

Success Criteria: When you run the code, you should NOT see "Copying buffer..." printed during the "Triggering resize..." phase. You should only see the allocation messages.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.