C++ Core Guidelines
54 essential rules from the Core Guidelines by Bjarne Stroustrup and Herb Sutter — annotated with the reasoning behind each rule and cross-linked to deeper explanations.
Official source
The complete guidelines are maintained on GitHub at isocpp.github.io/CppCoreGuidelines. This page is an annotated index of the most important rules with bigcpp cross-links.
Enforcing the guidelines
Run clang-tidy -checks=cppcoreguidelines-* to catch many violations automatically. Use the Guidelines Support Library (GSL) for types like gsl::not_null, gsl::span, and gsl::narrow_cast.
P. Philosophy
8 rulesHigh-level principles that underpin all other guidelines.
P.1Express ideas directly in code
Code that reflects intent is easier to verify, optimize, and maintain than code that obscures it behind raw loops or pointers.
P.2Write in ISO Standard C++
Extensions and vendor-specific features create portability debt and undefined behavior exposure.
P.3Express intent
Algorithm names (ranges::sort, transform) say what you want; raw loops say how — prefer what.
P.4Ideally, a program should be statically type safe
Type safety catches entire classes of bugs at compile time; use std::variant over unions, gsl::span over raw arrays.
P.5Prefer compile-time checking to run-time checking
static_assert, constexpr, and concepts catch errors early; runtime checks carry overhead and can be skipped.
P.9Don't waste time or space
Zero-overhead is C++'s core promise — don't use abstractions that cost more than direct code.
P.10Prefer immutable data to mutable data
const and constexpr eliminate entire categories of concurrency bugs and enable better optimization.
P.12Use supporting tools as appropriate
Static analyzers (clang-tidy), sanitizers (ASan/UBSan), and fuzzers find bugs compilers miss.
I. Interfaces
7 rulesHow to design function and class boundaries that are hard to misuse.
I.1Make interfaces explicit
Implicit dependencies (globals, thread-locals) make code untestable and order-dependent.
I.4Make interfaces precisely and strongly typed
Avoid bool flags and integer overloads; prefer strong types or enums so misuse doesn't compile.
I.23Keep the number of function arguments low
More than 4 arguments suggests a missing abstraction; group related parameters into a struct.
I.30Encapsulate rule violations
If you must use a raw pointer or unsynchronized access, isolate it in a well-documented, well-tested class.
F. Functions
7 rulesRules for writing functions that are correct, efficient, and easy to reason about.
F.1Package meaningful operations as carefully named functions
Functions are the unit of reuse, testing, and documentation — prefer them over inline lambda soup.
F.15Prefer simple and conventional ways of passing information
In: T const& or by value. Out/in-out: T&. Ownership transfer: unique_ptr<T>. Avoid output parameters.
F.16For 'in' parameters, pass cheaply-copied types by value and others by reference to const
Pass int/double/bool by value; pass std::string, std::vector by const&.
F.20For 'out' output values, prefer return values to output parameters
Return values are clearer, composable, and work with structured bindings and [[nodiscard]].
F.42Return a T* to indicate a position (only)
Returning a raw pointer signals non-owning; use optional<T&> or span for optional elements.
F.51Where there is a choice, prefer default arguments over overloading
Default arguments keep the interface in one place; overloading multiplies documentation burden.
C. Classes
7 rulesObject-oriented design for correct resource management and value semantics.
C.2Use class if the class has an invariant; use struct if the data members can vary independently
struct signals a plain data aggregate; class signals an abstraction with enforced invariants.
C.35A base class destructor should be either public and virtual, or protected and non-virtual
Deleting a derived object through a base pointer without a virtual destructor is undefined behavior.
C.48Prefer in-class initializers to member initializers in constructors for constant initializers
In-class initializers are declared once and apply to all constructors automatically.
C.60Make copy assignment non-virtual, take the parameter by const&, and return by non-const&
This is the conventional signature; deviations confuse users and compilers.
C.80Use =default if you have to be explicit about using the default semantics
=default generates correct, efficient code; writing the body manually is error-prone.
R. Resource Management
7 rulesHow to own, share, and release resources without leaks or dangling pointers.
R.5Prefer scoped objects; don't heap-allocate unnecessarily
Stack allocation is faster, safer, and doesn't require deallocation — use it by default.
R.10Avoid malloc() and free()
malloc bypasses constructors; free bypasses destructors. Use new/delete — or better, smart pointers.
ES. Expressions and Statements
7 rulesDay-to-day coding rules for clarity, correctness, and avoiding UB.
ES.1Prefer the standard library to other libraries and 'handcrafted code'
STL algorithms are optimized, tested, and communicate intent clearly to any C++ reader.
ES.5Keep scopes small
Shorter scope → fewer variables in play → fewer aliasing bugs → easier reasoning.
ES.10Declare one name (only) per declaration
int* p, q; — q is not a pointer. Separate declarations eliminate this class of surprise.
ES.20Always initialize an object
Uninitialized variables are undefined behavior in C++; zero-initialize or declare at point of first use.
ES.25Declare an object const or constexpr unless you want to modify its value later
const variables are immutable, more optimizable, and document intent.
ES.65Don't dereference an invalid pointer
Dereferencing nullptr or a dangling pointer is undefined behavior — use references or optional.
CP. Concurrency
6 rulesThread safety, data races, and synchronization rules.
CP.21Use std::lock() or std::scoped_lock to acquire multiple mutexes
Acquiring multiple mutexes one-by-one in different order across threads causes deadlock.
CP.31Pass small amounts of data between threads by value, larger amounts by (smart) pointer
Shared mutable state is the root of data races — prefer message passing or const shared_ptr.
CP.40Minimize context switching
Context switches and cache misses dominate concurrent performance — design to minimize lock contention.
E. Error Handling
5 rulesWhen to throw, when to return errors, and how to write exception-safe code.
E.1Develop an error-handling strategy early in a design
Mixing exceptions and error codes in an interface makes it impossible to write correct callers.
E.12Use noexcept when exiting a function because of a throw is impossible or unacceptable
noexcept allows destructors and move operations to be used in more contexts safely.
E.17Don't try to catch every exception in every function
Only catch exceptions you can handle; let others propagate to a handler that can.
E.28Avoid error handling based on global state (e.g., errno)
Global state is not thread-safe and creates hidden dependencies between functions.
The C++ Core Guidelines are licensed under CC-BY 4.0. Rule text and annotations on this page are original summaries, not verbatim copies.