Write C++ code following Herb Sutter's exceptional C++ principles. Emphasizes exception safety, const correctness, modern idioms, and defensive programming...
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++.
"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.
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.
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++) |
swap operations noexceptnoexceptnoexcept when possibleconst member functions when state isn't modifiedauto for complex types, explicit types for documentationconst_cast to remove const from const datamake_unique/make_shared over newstd::optional over pointer-or-null patternsstd::variant over union + type tagclass Stack {
T* data_;
size_t size_;
size_t capacity_;
public:
// STRONG guarantee via copy-and-swap
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_);
}
// STRONG guarantee for push
void push(const T& value) {
if (size_ == capacity_) {
// Create new buffer first (might throw)
Stack temp;
temp.reserve(capacity_ * 2);
for (size_t i = 0; i < size_; ++i)
temp.data_[i] = data_[i];
temp.size_ = size_;
// Commit phase (noexcept)
swap(*this, temp);
}
data_[size_++] = value;
}
};
class Widget {
std::vector<int> data_;
mutable std::mutex mutex_; // mutable: okay for logical const
public:
// Const member function: promises not to modify logical state
std::vector<int> getData() const {
std::lock_guard<std::mutex> lock(mutex_); // mutable allows this
return data_; // Return copy
}
// Non-const overload when modification needed
std::vector<int>& data() { return data_; }
// Const ref for read-only access (no copy)
const std::vector<int>& data() const { return data_; }
};
// widget.h
#include <memory>
class Widget {
public:
Widget();
~Widget(); // Defined in .cpp
Widget(Widget&&) noexcept; // Defined in .cpp
Widget& operator=(Widget&&) noexcept; // Defined in .cpp
Widget(const Widget&); // Defined in .cpp
Widget& operator=(const Widget&); // Defined in .cpp
void doSomething();
private:
struct Impl;
std::unique_ptr<Impl> pImpl_;
};
// widget.cpp
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(const Widget& other)
: pImpl_(std::make_unique<Impl>(*other.pImpl_)) {}
Widget& Widget::operator=(const Widget& other) {
*pImpl_ = *other.pImpl_;
return *this;
}
void Widget::doSomething() { pImpl_->doSomethingImpl(); }
Sutter thinks in terms of contracts and guarantees:
Key lessons from Guru of the Week:
++i to i++#include dependenciesusing namespace in headers