What You'll Learn
- The three exception safety levels
- Copy-and-swap for strong guarantee
- When and how to use noexcept
- Writing exception-safe classes
Exception Safety Levels
Every C++ function provides one of three exception safety guarantees: basic (no leaks), strong (all-or-nothing), or no-throw (never fails). Understanding these levels is essential for writing robust, production-quality code.
Basic Guarantee — No Leaks, Valid State
The basic guarantee promises that if an exception is thrown, no resources leak and the object remains in a valid (but possibly modified) state. Most standard library operations provide at least the basic guarantee.
Think of it like a database without transactions — if the power goes out, data is consistent but some changes may be partial. RAII ensures the "no leaks" part automatically.
Basic Guarantee
Demonstrate basic exception safety with vector operations
#include <iostream>
#include <vector>
#include <stdexcept>
using namespace std;
// Basic guarantee: no leaks, but state may change
class UserList {
vector<string> users;
public:
void addUser(const string& name) {
if (name.empty())
throw invalid_argument("Name cannot be empty");
users.push_back(name); // may throw bad_alloc
cout << "Added: " << name << endl;
}
void printAll() const {
for (const auto& u : users)
cout << "
...Strong Guarantee — Copy and Swap
The strong guarantee promises that if an operation fails, the program state is exactly as it was before the call. The classic technique is copy-and-swap: do all the risky work on a copy, then swap (which is noexcept) only on success.
Pro Tip: The strong guarantee has a cost — you're copying data. Use it for critical operations (financial transactions, config updates) and basic guarantee for hot paths where performance matters more.
Strong Guarantee
All-or-nothing batch operations with copy-and-swap
#include <iostream>
#include <vector>
#include <stdexcept>
#include <algorithm>
using namespace std;
// Strong guarantee via copy-and-swap
class TransactionLog {
vector<string> entries;
public:
// Strong guarantee: all-or-nothing
void addBatch(const vector<string>& batch) {
// Work on a copy — original untouched if anything throws
vector<string> temp = entries; // copy
for (const auto& entry : batch) {
if (entry.empty())
throw inv
...No-Throw Guarantee — noexcept
Functions marked noexcept promise to never throw. If they do, std::terminate is called immediately. The compiler uses noexcept to optimize — most critically, std::vector uses move instead of copy during reallocation only if the move constructor is noexcept.
Common Mistake: Forgetting noexcept on move constructors. Without it, vector::push_back falls back to copying, killing performance for types with expensive copies.
noexcept Moves
See how noexcept enables vector to use move operations
#include <iostream>
#include <utility>
#include <vector>
using namespace std;
class Buffer {
int* data;
size_t sz;
public:
Buffer(size_t n) : data(new int[n]{}), sz(n) {
cout << "Allocated " << n << " ints" << endl;
}
~Buffer() {
delete[] data;
cout << "Freed buffer" << endl;
}
// noexcept move constructor — STL containers prefer this
Buffer(Buffer&& other) noexcept
: data(other.data), sz(other.sz) {
other.data = nullptr;
...Quick Reference
| Level | Promise | Technique |
|---|---|---|
| Basic | No leaks, valid state | RAII |
| Strong | All-or-nothing | Copy-and-swap |
| No-throw | Never throws | noexcept keyword |
Lesson Complete!
You now understand all three exception safety levels and can write classes that provide the appropriate guarantee.
Sign up for free to track which lessons you've completed and get learning reminders.