Question

When to Use noexcept in C++: Practical Rules, Performance, and Best Practices

cppc++11exceptionnoexcept

Question

In C++, the noexcept keyword can be added to many function declarations, but it is not always clear when it should be used in practice.

I understand that noexcept became especially important for cases such as move constructors, where throwing can affect how standard library containers behave. However, I still have several practical questions:

  • If I have functions that I know will never throw, but the compiler cannot prove that fact, should I mark all of them with noexcept?
  • It seems impractical to analyze every single function declaration in a codebase. In which situations should I be especially careful to use noexcept, and when is leaving the default behavior acceptable?
  • When does noexcept realistically improve performance?
  • Can you give an example where adding noexcept lets a C++ compiler generate better code?
  • Do modern C++ compilers and the standard library actually take advantage of noexcept for optimization, especially around move operations?

For example, consider a simple function like this:

int square(int x) noexcept {
    return x * x;
}

and compare it with a similar function without noexcept:

int square(int x) {
    return x * x;
}

When does the noexcept promise matter in real code, and how should I decide where to use it?

Short Answer

By the end of this page, you will understand what noexcept means in C++, when it is worth adding, why it matters for move constructors and standard containers, and where it usually does not produce meaningful speedups. You will also learn practical rules for using it safely in real codebases.

Concept

noexcept is a promise: this function will not allow an exception to escape.

A function marked noexcept can still contain code that might throw internally, but if an exception does escape the function, the program calls std::terminate() instead of normal exception propagation.

void f() noexcept {
    throw 42; // Compiles, but if this exception escapes, the program terminates.
}

That means noexcept is not just documentation. It changes program behavior.

Why noexcept matters

In modern C++, noexcept is most important in places where the standard library or generic code needs to decide between:

  • moving an object
  • copying an object
  • or refusing an operation entirely

A classic example is std::vector. When a vector grows and must relocate its elements, it prefers moving them for efficiency. But if moving might throw and copying is available, the vector may choose copying instead, because copying can preserve stronger exception safety guarantees.

If a type has a move constructor marked noexcept, containers can safely use it during reallocation.

What noexcept does not mean

It does not mean:

  • the function is always faster
  • the compiler can magically optimize everything better
  • every non-throwing function should always be marked immediately

In practice, noexcept is most valuable where exception safety affects overload resolution, generic behavior, container performance, and API contracts.

The key idea

Use noexcept when non-throwing behavior is an important and stable part of the function's contract.

This is especially true for:

  • move constructors
  • move assignment operators
  • swap functions
  • destructors
  • small low-level utility functions that must not fail
  • functions used by generic code that checks noexcept

For ordinary business logic functions, leaving them without noexcept is often fine unless you specifically want to make and enforce that guarantee.

Mental Model

Think of noexcept as putting a sealed "no exceptions may leave this room" sign on a function.

  • Without the sign, an exception can leave the room and be handled somewhere else.
  • With the sign, if an exception tries to leave, the building alarm goes off and the program is immediately stopped with std::terminate().

Why would you ever want that sign?

Because other parts of the program can trust it.

For example, a std::vector moving elements during resize wants to know:

  • "If I move these objects, can something fail halfway through?"
  • If the answer is no, it can move confidently.
  • If the answer is maybe, it may choose a safer but slower strategy, such as copying.

So noexcept is not mainly about saving a few CPU instructions. It is about giving strong guarantees that other code can rely on.

Take Quiz

Syntax and Examples

Basic syntax

void logMessage() noexcept {
    // guaranteed not to let exceptions escape
}

You can also make it conditional:

template <typename T>
void swapValues(T& a, T& b) noexcept(noexcept(T(std::move(a)))) {
    T temp = std::move(a);
    a = std::move(b);
    b = std::move(temp);
}

The inner noexcept(...) checks whether an expression can throw. The outer noexcept(...) uses that result to declare whether the function itself is non-throwing.

Beginner-friendly example

#include <iostream>

class Counter {
public:
    Counter(int v) noexcept : value(v) {}

    int get() const noexcept {
        return value;
    }

    void increment() noexcept {
        ++value;
    }

private:
    int value;
};

int main() {
    Counter c(5);
    c.increment();
    std::cout << c.get() << '\n';
}

This is a good use of noexcept because:

  • constructing from an int cannot throw here
  • reading the value cannot throw
  • incrementing an int cannot throw

These are simple operations with a clear non-throwing contract.

Important example: move constructor

#include <vector>
#include <string>

class Buffer {
public:
    Buffer() = default;

    Buffer(Buffer&& other) noexcept
        : data(std::move(other.data)) {}

    Buffer& operator=(Buffer&& other) noexcept {
        data = std::move(other.data);
        return *this;
    }

private:
    std::string data;
};

Marking move operations noexcept is often important because containers like std::vector<Buffer> can then move Buffer objects during reallocation instead of falling back to copying.

Conditional noexcept

This is common in templates:

#include <utility>

template <typename T>
class Box {
public:
    Box(Box&& other) noexcept(noexcept(T(std::move(other.value))))
        : value(std::move(other.value)) {}

private:
    T value;
};

This means: the move constructor of Box<T> is noexcept only if moving T is also noexcept.

That is how generic libraries preserve correctness while still giving strong guarantees when possible.

Step by Step Execution

Consider this example:

#include <iostream>
#include <vector>

class Item {
public:
    Item() = default;

    Item(const Item&) {
        std::cout << "copy\n";
    }

    Item(Item&&) noexcept {
        std::cout << "move\n";
    }
};

int main() {
    std::vector<Item> items;
    items.reserve(1);

    items.push_back(Item{});
    items.push_back(Item{});
}

What happens step by step

1. items.reserve(1);

The vector allocates enough space for 1 Item.

2. items.push_back(Item{});

A temporary Item is created.

The vector places it into its storage. Since the temporary is an rvalue, moving is used.

Output:

move

3. items.push_back(Item{});

Now the vector is full. It needs more storage.

To grow, it allocates a new larger block and relocates the existing element.

Because Item's move constructor is marked noexcept, the vector can safely move the existing element into the new storage.

Then it moves the new temporary into place.

Typical output:

move
move
move

One move comes from the first insertion. Two more moves happen during the second insertion:

  • move old element to new storage
  • move new temporary into new storage

What if the move constructor were not noexcept?

If the move constructor might throw and a copy constructor is available, many standard library implementations will prefer copying existing elements during reallocation.

That changes behavior conceptually to:

move
copy
move

The exact output depends on implementation details, but the important lesson is this:

  • noexcept on move operations affects how containers manage elements
  • this can influence both exception safety and performance

Real World Use Cases

1. Move-enabled classes stored in containers

If you write a class that owns resources and instances will live inside std::vector, std::deque, or similar containers, noexcept on move operations is often important.

Examples:

  • file buffer wrappers
  • image or audio buffers
  • network packet objects
  • parsed data records

2. swap functions

A custom swap is often expected to be non-throwing.

class Widget {
public:
    void swap(Widget& other) noexcept {
        std::swap(id, other.id);
    }

private:
    int id{};
};

Algorithms and generic code often work better when swap is known not to throw.

3. Destructors and cleanup logic

Destructors should not throw. In modern C++, destructors are effectively non-throwing by default unless declared otherwise, and throwing from a destructor during stack unwinding is dangerous.

Typical examples:

  • releasing memory
  • closing file handles
  • unlocking mutexes

4. Low-level utility functions

Small functions that simply inspect state or perform arithmetic are often good candidates.

class Point {
public:
    int x() const noexcept { return x_; }
    int y() const noexcept { return y_; }

private:
    int x_{};
    int y_{};
};

5. Generic library code

Template code often uses noexcept to preserve guarantees from the types it works with.

Examples:

  • containers
  • smart pointer utilities
  • tuple/pair helpers
  • custom wrapper types
Take Quiz

Real Codebase Usage

In real projects, developers usually do not try to mark every function noexcept.

Instead, they focus on places where it matters most.

Common patterns

1. Mark move operations noexcept when true

class Session {
public:
    Session(Session&&) noexcept = default;
    Session& operator=(Session&&) noexcept = default;
};

This is one of the most common and valuable uses.

2. Mark swap as noexcept

class Session {
public:
    void swap(Session& other) noexcept {
        std::swap(handle, other.handle);
    }

private:
    int handle{};
};

3. Use conditional noexcept in templates

template <typename T>
void resetToDefault(T& value) noexcept(noexcept(T{})) {
    value = T{};
}

This lets generic code stay accurate.

4. Use it for small accessors and utility functions

class Config {
public:
    bool enabled() const noexcept { return enabled_; }

private:
    bool enabled_{};
};

5. Avoid it in code that may reasonably evolve to throw

For example, parsing, validation, file loading, database calls, and network code often should not be marked noexcept, because failure is part of the domain.

std::string loadConfigFile(const std::string& path); // not noexcept

Guard clauses and error handling

If a function is marked noexcept, you must handle all failures internally.

That often means:

  • returning error codes
  • storing error state
  • logging and terminating
  • checking preconditions up front
bool tryParseInt(const std::string& s, int& out) noexcept {
    try {
        out = std::stoi(s);
        return true;
    } catch (...) {
        return false;
    }
}

This is a deliberate API choice: errors do not escape as exceptions.

Practical rule used in codebases

A useful team rule is:

  • Use noexcept for operations that conceptually cannot fail
  • Prioritize move operations, swap, destructors, and trivial accessors
  • Do not add it speculatively to complex functions

Common Mistakes

1. Marking a function noexcept just because it usually does not throw

A function should be noexcept only if you are willing to make a strong promise that exceptions will never escape.

Broken example:

std::string readFile(const std::string& path) noexcept {
    std::ifstream file(path);
    if (!file) {
        throw std::runtime_error("Cannot open file");
    }
    return "data";
}

If an exception escapes, the program terminates.

Better:

std::string readFile(const std::string& path) {
    std::ifstream file(path);
    if (!file) {
        throw std::runtime_error("Cannot open file");
    }
    return "data";
}

2. Assuming noexcept automatically makes code faster

Usually, the biggest practical performance effect is through library behavior, especially move operations in containers. It is not a universal speed boost.

3. Forgetting that noexcept changes failure behavior

Broken example:

void update() noexcept {
    riskyOperation();
}

If riskyOperation() throws, the program ends.

Avoid this unless that is exactly what you want.

4. Not using conditional noexcept in templates

Broken example:

template <typename T>
void process(T& value) noexcept {
    T copy = value; // copy may throw
}

Better:

template <typename T>
void process(T& value) noexcept(noexcept(T(value))) {
    T copy = value;
}

5. Ignoring move constructors and move assignment

If your type is movable and meant for standard containers, this is one of the first places to check.

class Data {
public:
    Data(Data&&) noexcept = default;
    Data& operator=(Data&&) noexcept = default;
};

6. Overusing noexcept in application-level logic

Business logic often needs to report failure naturally. Overusing noexcept can make code more rigid and less maintainable.

Comparisons

ConceptMeaningIf exception escapesCommon use
No specificationFunction may throwNormal exception propagationGeneral-purpose functions
noexceptFunction promises not to let exceptions escapestd::terminate()Move ops, swap, accessors, low-level utilities
noexcept(condition)Promise depends on a compile-time conditionstd::terminate() when declared non-throwingTemplates and generic code

noexcept vs old-style throw()

Featurethrow()noexcept
Modern C++ useDeprecated/removedPreferred
MeaningOld exception specificationNon-throwing guarantee
Tooling/library supportLegacyStandard modern practice

noexcept vs not writing it

ChoiceBest when
Use noexceptThe function truly should never fail by exception, and this guarantee matters to callers or generic code
Leave it offThe function may throw now or in the future, or the guarantee adds little value

Where noexcept matters most

Function typeUsually use noexcept?Why
DestructorYes, effectively non-throwingThrowing is dangerous during unwinding
Move constructorOften yesHelps containers move safely
Move assignmentOften yesSame reason
swapOften yesGeneric algorithms prefer non-throwing swap
Getter/accessorOften yes if truly trivialClear API contract
Parser / file loader / network callUsually noFailure is expected and must be reported
Take Quiz

Cheat Sheet

Quick rules

  • noexcept means: no exception may escape this function.
  • If an exception does escape, the program calls std::terminate().
  • Use it when non-throwing behavior is part of the function contract.
  • Most valuable for move operations, swap, destructors, and trivial accessors.
  • Do not add it to functions that may reasonably need to throw.

Syntax

void f() noexcept;

void g() noexcept(true);
void h() noexcept(false);

template <typename T>
void func() noexcept(noexcept(T{}));

Best candidates

class A {
public:
    A(A&&) noexcept = default;
    A& operator=(A&&) noexcept = default;

    int value() const noexcept { return v; }

    void swap(A& other) noexcept {
        std::swap(v, other.v);
    }

private:
    int v{};
};

Avoid using noexcept when

  • the function performs I/O and may fail
  • it parses or validates input and may reject data
  • it allocates memory in ways you do not want to treat as fatal
  • the implementation may evolve to throw later

Important effect

For containers like std::vector:

  • noexcept move constructor -> vector can move elements during reallocation
  • potentially throwing move constructor -> vector may copy instead

Expression check

noexcept(expression)

This returns whether the expression is known not to throw.

Example:

static_assert(noexcept(std::declval<int&>() = 5));

Simple decision guide

  • Is this operation conceptually unable to fail? -> consider noexcept
  • Will generic code or containers benefit from knowing that? -> strongly consider noexcept
  • Could callers reasonably expect exceptions from this function? -> leave it off

FAQ

What does noexcept do in C++?

It declares that a function will not let exceptions escape. If one does, the program terminates instead of propagating the exception.

Should I mark every non-throwing function noexcept?

No. Focus on functions where the guarantee matters: move operations, swap, destructors, and simple accessors. For many ordinary functions, leaving it off is fine.

Does noexcept improve performance?

Sometimes, but usually indirectly. The most important real effect is that standard containers may use move operations more aggressively when those moves are noexcept.

Why does std::vector care about noexcept?

During reallocation, vector wants to preserve exception safety. If moving elements might throw, it may choose copying instead. If moving is noexcept, moving becomes safer to use.

Can a noexcept function still contain code that throws?

Yes. But if an exception escapes the function, std::terminate() is called.

Is noexcept checked at compile time?

The declaration is checked syntactically, but the compiler does not prove all code paths are non-throwing in the general case. You are making a promise.

Should getters be noexcept?

Usually yes, if they simply return stored values and cannot throw.

Should constructors be noexcept?

Only when construction truly cannot fail. Many constructors allocate memory or validate inputs, so they should not automatically be marked noexcept.

Take Quiz

Mini Project

Description

Build a small movable C++ type and store it in a std::vector to observe how noexcept affects container behavior during reallocation. This project demonstrates one of the most practical reasons to use noexcept in everyday C++: enabling efficient and safe moves.

Goal

Create a class with copy and move operations, push objects into a vector, and observe when moves are preferred because the move constructor is marked noexcept.

Requirements

  • Create a class that prints when it is copied or moved.
  • Store objects of that class in a std::vector.
  • Trigger vector reallocation by pushing multiple elements.
  • Mark the move constructor and move assignment operator with noexcept.
  • Compare the behavior with and without noexcept on the move operations.
Take Quiz

Keep learning

Related questions

View more