noexcept promises a function won't throw — enabling optimisations like moving elements during vector growth. Exception-safety guarantees describe what state is left behind when something does throw.
noexcept
#include <iostream>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
int safeAdd(int a, int b) noexcept { return a + b; }
void mayThrow() { throw 1; }
template <typename T>
void swapValues(T& a, T& b) noexcept(std::is_nothrow_move_constructible_v<T>) { // conditional
T tmp = std::move(a);
a = std::move(b);
b = std::move(tmp);
}
int main() {
std::cout << std::boolalpha
<< noexcept(safeAdd(1, 2)) << ' ' // noexcept operator: compile-time query
<< noexcept(mayThrow()) << ' '
<< std::is_nothrow_move_constructible_v<std::string> << ' '
<< std::is_nothrow_move_constructible_v<std::vector<int>> << '\n';
std::string a = "x", b = "y";
swapValues(a, b);
std::cout << a << b << '\n';
}true false true true
yx#include <stdexcept>
void promise() noexcept {
throw std::runtime_error("broken promise"); // compiles, but calls std::terminate at run time
}
int main() { promise(); }Destructors,
operator deleteand (by default) compiler-generated move operations of well-behaved members arenoexcept.Mark move constructors/assignments and
swapnoexcept— containers depend on it.Don't mark functions
noexceptif they might throw someday; removing it later breaks callers that relied on it.
The exception-safety guarantees
| Guarantee | Promise if an exception is thrown | Example |
|---|---|---|
| No-throw | Never throws | Destructors, swap, move operations |
| Strong | Commit-or-rollback: state is unchanged | vector::push_back (when the move ctor is noexcept or copyable) |
| Basic | No leaks, invariants hold, but state may have changed | Most operations |
| None | Anything goes — avoid | Code with raw new/delete |
Getting the strong guarantee: copy-and-swap
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
class Inventory {
std::vector<std::string> items_;
public:
void addAll(const std::vector<std::string>& newItems) {
std::vector<std::string> copy = items_; // 1. work on a copy
for (const auto& it : newItems) {
if (it.empty()) throw std::invalid_argument("empty item");
copy.push_back(it);
}
items_.swap(copy); // 2. commit with a no-throw swap
}
std::size_t size() const { return items_.size(); }
};
int main() {
Inventory inv;
inv.addAll({"pen", "ink"});
try { inv.addAll({"cap", "", "pad"}); } catch (const std::exception& e) { std::cout << e.what() << '\n'; }
std::cout << inv.size() << '\n'; // still 2: nothing half-added
}empty item
2Why are destructors implicitly noexcept?