Skip to content
C++

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 rules

High-level principles that underpin all other guidelines.

P.1

Express 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.2

Write in ISO Standard C++

Extensions and vendor-specific features create portability debt and undefined behavior exposure.

P.3

Express intent

Algorithm names (ranges::sort, transform) say what you want; raw loops say how — prefer what.

P.4

Ideally, 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.5

Prefer compile-time checking to run-time checking

static_assert, constexpr, and concepts catch errors early; runtime checks carry overhead and can be skipped.

P.9

Don't waste time or space

Zero-overhead is C++'s core promise — don't use abstractions that cost more than direct code.

P.10

Prefer immutable data to mutable data

const and constexpr eliminate entire categories of concurrency bugs and enable better optimization.

P.12

Use supporting tools as appropriate

Static analyzers (clang-tidy), sanitizers (ASan/UBSan), and fuzzers find bugs compilers miss.

I. Interfaces

7 rules

How to design function and class boundaries that are hard to misuse.

I.1

Make interfaces explicit

Implicit dependencies (globals, thread-locals) make code untestable and order-dependent.

I.3

Avoid singletons

Singletons create hidden global state, order-of-initialization problems, and untestable code.

I.4

Make interfaces precisely and strongly typed

Avoid bool flags and integer overloads; prefer strong types or enums so misuse doesn't compile.

I.11

Never transfer ownership by a raw pointer (T*) or reference (T&)

Pass ownership via unique_ptr; pass non-owning views via raw pointer or reference — be explicit.

I.13

Do not pass an array as a single pointer

Use std::span — it carries the size with the pointer, eliminating buffer overruns.

I.23

Keep the number of function arguments low

More than 4 arguments suggests a missing abstraction; group related parameters into a struct.

I.30

Encapsulate rule violations

If you must use a raw pointer or unsynchronized access, isolate it in a well-documented, well-tested class.

F. Functions

7 rules

Rules for writing functions that are correct, efficient, and easy to reason about.

F.1

Package meaningful operations as carefully named functions

Functions are the unit of reuse, testing, and documentation — prefer them over inline lambda soup.

F.6

If your function must not throw, declare it noexcept

noexcept allows better code generation and documents guarantees to callers.

F.15

Prefer 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.16

For '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.20

For 'out' output values, prefer return values to output parameters

Return values are clearer, composable, and work with structured bindings and [[nodiscard]].

F.42

Return a T* to indicate a position (only)

Returning a raw pointer signals non-owning; use optional<T&> or span for optional elements.

F.51

Where there is a choice, prefer default arguments over overloading

Default arguments keep the interface in one place; overloading multiplies documentation burden.

C. Classes

7 rules

Object-oriented design for correct resource management and value semantics.

C.2

Use 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.20

If you can avoid defining any default operations, do

The Rule of Zero: let the compiler generate copy/move/destructor when all members manage themselves.

C.31

All resources acquired by a class must be released by the class's destructor

RAII: every resource has an owner; the destructor is the automatic cleanup guarantee.

C.35

A 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.48

Prefer in-class initializers to member initializers in constructors for constant initializers

In-class initializers are declared once and apply to all constructors automatically.

C.60

Make 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.80

Use =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 rules

How to own, share, and release resources without leaks or dangling pointers.

R.1

Manage resources automatically using resource handles and RAII

Every resource should have an owning object whose destructor releases it — no exceptions (literally).

R.3

A raw pointer (T*) is non-owning

If a pointer owns a resource, use unique_ptr. Raw pointers are for non-owning access only.

R.5

Prefer scoped objects; don't heap-allocate unnecessarily

Stack allocation is faster, safer, and doesn't require deallocation — use it by default.

R.10

Avoid malloc() and free()

malloc bypasses constructors; free bypasses destructors. Use new/delete — or better, smart pointers.

R.11

Avoid calling new and delete explicitly

make_unique and make_shared provide exception safety and eliminate the new/delete imbalance class of bugs.

R.20

Use unique_ptr or shared_ptr to represent ownership

unique_ptr for exclusive ownership; shared_ptr for shared. Do not invent your own smart pointer.

R.21

Prefer unique_ptr over shared_ptr unless you need to share ownership

unique_ptr is zero-overhead; shared_ptr has atomic reference count cost. Only pay for what you need.

ES. Expressions and Statements

7 rules

Day-to-day coding rules for clarity, correctness, and avoiding UB.

ES.1

Prefer the standard library to other libraries and 'handcrafted code'

STL algorithms are optimized, tested, and communicate intent clearly to any C++ reader.

ES.5

Keep scopes small

Shorter scope → fewer variables in play → fewer aliasing bugs → easier reasoning.

ES.10

Declare one name (only) per declaration

int* p, q; — q is not a pointer. Separate declarations eliminate this class of surprise.

ES.20

Always initialize an object

Uninitialized variables are undefined behavior in C++; zero-initialize or declare at point of first use.

ES.25

Declare an object const or constexpr unless you want to modify its value later

const variables are immutable, more optimizable, and document intent.

ES.46

Avoid narrowing conversions

double→int silently loses data. Use explicit casts or gsl::narrow_cast which asserts the value fits.

ES.65

Don't dereference an invalid pointer

Dereferencing nullptr or a dangling pointer is undefined behavior — use references or optional.

CP. Concurrency

6 rules

Thread safety, data races, and synchronization rules.

CP.1

Assume that your code will run as part of a multi-threaded program

Global/static state, non-const shared data, and lock-free patterns all require explicit reasoning.

CP.2

Avoid data races

A data race on any non-atomic variable is undefined behavior — not just 'sometimes wrong'.

CP.20

Use RAII, never plain lock()/unlock()

std::lock_guard and std::unique_lock ensure the mutex is released even on exceptions.

CP.21

Use std::lock() or std::scoped_lock to acquire multiple mutexes

Acquiring multiple mutexes one-by-one in different order across threads causes deadlock.

CP.31

Pass 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.40

Minimize context switching

Context switches and cache misses dominate concurrent performance — design to minimize lock contention.

E. Error Handling

5 rules

When to throw, when to return errors, and how to write exception-safe code.

E.1

Develop an error-handling strategy early in a design

Mixing exceptions and error codes in an interface makes it impossible to write correct callers.

E.6

Use RAII to prevent leaks

Stack unwinding during exception propagation calls destructors — RAII makes this automatic.

E.12

Use 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.17

Don't try to catch every exception in every function

Only catch exceptions you can handle; let others propagate to a handler that can.

E.28

Avoid 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.