| name | sutter-exceptional-cpp |
| description | Write C++ code following Herb Sutter's exceptional C++ principles. Emphasizes exception safety, const correctness, modern idioms, and defensive programming. Use when writing robust, production-quality C++ that must handle errors gracefully. |
| tags | concurrency, move-semantics, smart-pointers, const, exceptions, guidelines, modern, best-practices |
Herb Sutter Style Guide
Overview
Herb Sutter chairs the ISO C++ standards committee and has shaped modern C++ more than almost anyone. His "Exceptional C++" series and "GotW" (Guru of the Week) columns defined how we think about exception safety, const correctness, and defensive C++.
Core Philosophy
"Don't optimize prematurely. Don't pessimize prematurely."
"Write for clarity and correctness first. Optimize measured bottlenecks."
Sutter believes in defensive programming: code that handles errors gracefully, maintains invariants, and fails safely when the unexpected happens.
Design Principles
-
Exception Safety is Non-Negotiable: Every function has an exception safety guarantee. Know which one yours provides.
-
Const Correctness: const isn't decoration—it's documentation and enforcement of intent.
-
Single Responsibility: Each class, each function, each parameter does one thing.
-
Value Semantics by Default: Prefer values over pointers. Prefer smart pointers over raw.
Exception Safety Guarantees
Every function provides one of these guarantees:
| Guarantee | Meaning |
|---|
| No-throw | Never throws. Destructors, swap, move operations should be here. |
| Strong | If exception thrown, state unchanged (commit or rollback) |
| Basic | If exception thrown, invariants preserved, no leaks, valid state |
| None | No guarantees (unacceptable in modern C++) |
When Writing Code
Always
- Know and document your exception safety guarantee
- Make
swap operations noexcept
- Make destructors
noexcept
- Make move operations
noexcept when possible
- Use
const member functions when state isn't modified
- Prefer
auto for complex types, explicit types for documentation
- Use RAII for all resources (no leak on any code path)
Never
- Throw from destructors
- Let exceptions escape callbacks/handlers without catch
- Write functions that provide no exception safety guarantee
- Use
const_cast to remove const from const data
- Return raw pointers to owned resources
Prefer
make_unique/make_shared over new
- Copy-and-swap for exception-safe assignment
- Algorithms over raw loops
std::optional over pointer-or-null patterns
std::variant over union + type tag
Code Patterns
Exception-Safe Assignment (Strong Guarantee)
class Stack {
T* data_;
size_t size_;
size_t capacity_;
public:
Stack& operator=(Stack other) noexcept {
swap(*this, other);
return *this;
}
friend void swap(Stack& a, Stack& b) noexcept {
using std::swap;
swap(a.data_, b.data_);
swap(a.size_, b.size_);
swap(a.capacity_, b.capacity_);
}
void push(const T& value) {
if (size_ == capacity_) {
Stack temp;
temp.reserve(capacity_ * 2);
for (size_t i = 0; i < size_; ++i)
temp.data_[i] = data_[i];
temp.size_ = size_;
swap(*this, temp);
}
data_[size_++] = value;
}
};
Const Correctness Patterns
class Widget {
std::vector<int> data_;
mutable std::mutex mutex_;
public:
std::vector<int> getData() const {
std::lock_guard<std::mutex> lock(mutex_);
return data_;
}
std::vector<int>& data() { return data_; }
const std::vector<int>& data() const { return data_; }
};
Pimpl Done Right (Sutter's Approach)
#include <memory>
class Widget {
public:
Widget();
~Widget();
Widget(Widget&&) noexcept;
Widget& operator=(Widget&&) noexcept;
Widget(const Widget&);
Widget& operator=(const Widget&);
void doSomething();
private:
struct Impl;
std::unique_ptr<Impl> pImpl_;
};
struct Widget::Impl {
std::string name;
std::vector<int> data;
void doSomethingImpl() { }
};
Widget::Widget() : pImpl_(std::make_unique<Impl>()) {}
Widget::~Widget() = default;
Widget::Widget(Widget&&) noexcept = default;
Widget& Widget::operator=(Widget&&) noexcept = default;
Widget::Widget( Widget& other)
: (std::<Impl>(*other.pImpl_)) {}
Widget& Widget::=( Widget& other) {
*pImpl_ = *other.pImpl_;
*;
}
{ pImpl_->(); }
Mental Model
Sutter thinks in terms of contracts and guarantees:
- Preconditions: What must be true when function is called?
- Postconditions: What is guaranteed after function returns?
- Exception guarantee: What happens if something throws?
- Thread safety: What synchronization is needed?
GotW Wisdom
Key lessons from Guru of the Week:
- Prefer
++i to i++
- Virtual functions should be private, rarely protected, only public for interfaces
- Minimize
#include dependencies
- Never write
using namespace in headers
- Make const-qualified overloads return const (or by value)