sutter-exceptional-cpp
npx skills add https://github.com/copyleftdev/sk1llz --skill sutter-exceptional-cpp
Agent 安装分布
Skill 文档
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:
constisn’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
swapoperationsnoexcept - Make destructors
noexcept - Make move operations
noexceptwhen possible - Use
constmember functions when state isn’t modified - Prefer
autofor 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_castto removeconstfromconstdata - Return raw pointers to owned resources
Prefer
make_unique/make_sharedovernew- Copy-and-swap for exception-safe assignment
- Algorithms over raw loops
std::optionalover pointer-or-null patternsstd::variantover union + type tag
Code Patterns
Exception-Safe Assignment (Strong Guarantee)
class 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;
}
};
Const Correctness Patterns
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_; }
};
Pimpl Done Right (Sutter’s Approach)
// 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(); }
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
++itoi++ - Virtual functions should be private, rarely protected, only public for interfaces
- Minimize
#includedependencies - Never write
using namespacein headers - Make const-qualified overloads return const (or by value)