C++ Coding Standards (C++ Core Guidelines)
Coding standards for modern C++ (C++17/20/23) derived from the C++ Core Guidelines. Enforces type safety, resource safety, immutability, and clarity.
When to Use
- Writing new C++ code (classes, functions, templates)
- Reviewing or refactoring existing C++ code
- Making architectural decisions in C++ projects
- Enforcing consistent style across a C++ codebase
- Choosing between language features (e.g.,
enum vs enum class, raw pointer vs smart pointer)
When Not to Use
- Non-C++ projects
- Legacy C codebases that cannot adopt modern C++ features
- Embedded/bare-metal contexts where specific guidelines conflict with hardware constraints (adapt selectively)
Cross-Cutting Principles
- RAII everywhere (P.8, R.1, E.6, CP.20): Bind resource lifetime to object lifetime
- Immutability by default (P.10, Con.1-5, ES.25): Start with
const/constexpr; mutability is the exception
- Type safety (P.4, I.4, ES.46-49, Enum.3): Use the type system to prevent errors at compile time
- Express intent (P.3, F.1, NL.1-2, T.10): Names, types, and concepts should communicate purpose
- Minimize complexity (F.2-3, ES.5, Per.4-5): Simple code is correct code
- Value semantics over pointer semantics (C.10, R.3-5, F.20, CP.31): Prefer returning by value and scoped objects
Philosophy & Interfaces (P., I.)
| Rule |
Summary |
| P.1 |
Express ideas directly in code |
| P.3 |
Express intent |
| P.4 |
Ideally, a program should be statically type safe |
| P.5 |
Prefer compile-time checking to run-time checking |
| P.8 |
Avoid leaking any resources |
| P.10 |
Prefer immutable data to mutable data |
| I.1 |
Make interfaces explicit |
| I.2 |
Avoid non-const global variables |
| I.4 |
Make interfaces precisely and strongly typed |
| I.11 |
Transfer ownership via smart pointers, not raw pointers or references |
| I.23 |
Keep the number of function arguments low |
See references/code-examples.md § Philosophy & Interfaces for examples.
Functions (F.*)
| Rule |
Summary |
| F.1 |
Package meaningful operations as carefully named functions |
| F.2 |
A function should perform a single logical operation |
| F.3 |
Keep functions short and simple |
| F.4 |
If a function might be evaluated at compile time, declare it constexpr |
| F.6 |
If your function must not throw, declare it noexcept |
| F.8 |
Prefer pure functions |
| F.16 |
For "in" parameters, pass cheaply-copied types by value and others by const& |
| F.20 |
For "out" values, prefer return values to output parameters |
| F.21 |
To return multiple "out" values, prefer returning a struct |
| F.43 |
Avoid returning a pointer or reference to a local object |
Anti-patterns to avoid:
- Returning
T&& from functions (F.45)
- Using
va_arg / C-style variadics (F.55)
- Capturing by reference in lambdas passed to other threads (F.53)
- Returning
const T which inhibits move semantics (F.49)
See references/code-examples.md § Functions for parameter passing and constexpr examples.
Classes & Class Hierarchies (C.*)
| Rule |
Summary |
| C.2 |
Use class if invariant exists; struct if data members vary independently |
| C.9 |
Minimize exposure of members |
| C.20 |
If you can avoid defining default operations, do (Rule of Zero) |
| C.21 |
If you define or =delete any copy/move/destructor, handle them all (Rule of Five) |
| C.35 |
Base class destructor: public virtual or protected non-virtual |
| C.41 |
A constructor should create a fully initialized object |
| C.46 |
Declare single-argument constructors explicit |
| C.67 |
A polymorphic class should suppress public copy/move |
| C.128 |
Virtual functions: specify exactly one of virtual, override, or final |
Anti-patterns to avoid:
- Calling virtual functions in constructors/destructors (C.82)
- Using
memset/memcpy on non-trivial types (C.90)
- Providing different default arguments for virtual function and overrider (C.140)
- Making data members
const or references, which suppresses move/copy (C.12)
See references/code-examples.md § Classes for Rule of Zero, Rule of Five, and class hierarchy examples.
Resource Management (R.*)
| Rule |
Summary |
| R.1 |
Manage resources automatically using RAII |
| R.3 |
A raw pointer (T*) is non-owning |
| R.5 |
Prefer scoped objects; avoid heap-allocating unnecessarily |
| R.10 |
Avoid malloc()/free() |
| R.11 |
Avoid calling new and delete explicitly |
| R.20 |
Use unique_ptr or shared_ptr to represent ownership |
| R.21 |
Prefer unique_ptr over shared_ptr unless sharing ownership |
| R.22 |
Use make_shared() to make shared_ptrs |
Anti-patterns to avoid:
- Naked
new/delete (R.11)
malloc()/free() in C++ code (R.10)
- Multiple resource allocations in a single expression (R.13 — exception safety hazard)
shared_ptr where unique_ptr suffices (R.21)
See references/code-examples.md § Resource Management for smart pointer and RAII examples.
Expressions & Statements (ES.*)
| Rule |
Summary |
| ES.5 |
Keep scopes small |
| ES.20 |
Always initialize an object |
| ES.23 |
Prefer {} initializer syntax |
| ES.25 |
Declare objects const or constexpr unless modification is intended |
| ES.28 |
Use lambdas for complex initialization of const variables |
| ES.45 |
Avoid magic constants; use symbolic constants |
| ES.46 |
Avoid narrowing/lossy arithmetic conversions |
| ES.47 |
Use nullptr rather than 0 or NULL |
| ES.48 |
Avoid casts |
| ES.50 |
Avoid casting away const |
Anti-patterns to avoid:
- Uninitialized variables (ES.20)
- Using
0 or NULL as pointer (ES.47 — use nullptr)
- C-style casts (ES.48 — use
static_cast, const_cast, etc.)
- Casting away
const (ES.50)
- Magic numbers without named constants (ES.45)
- Mixing signed and unsigned arithmetic (ES.100)
- Reusing names in nested scopes (ES.12)
See references/code-examples.md § Expressions & Statements for initialization examples.
Error Handling (E.*)
| Rule |
Summary |
| E.1 |
Develop an error-handling strategy early in a design |
| E.2 |
Throw an exception to signal that a function can't perform its assigned task |
| E.6 |
Use RAII to prevent leaks |
| E.12 |
Use noexcept when throwing is impossible or unacceptable |
| E.14 |
Use purpose-designed user-defined types as exceptions |
| E.15 |
Throw by value, catch by reference |
| E.16 |
Destructors, deallocation, and swap must not fail |
| E.17 |
Avoid trying to catch every exception in every function |
Anti-patterns to avoid:
- Throwing built-in types like
int or string literals (E.14)
- Catching by value (slicing risk) (E.15)
- Empty catch blocks that silently swallow errors
- Using exceptions for flow control (E.3)
- Error handling based on global state like
errno (E.28)
See references/code-examples.md § Error Handling for exception hierarchy examples.
Constants & Immutability (Con.*)
| Rule |
Summary |
| Con.1 |
By default, make objects immutable |
| Con.2 |
By default, make member functions const |
| Con.3 |
By default, pass pointers and references to const |
| Con.4 |
Use const for values that don't change after construction |
| Con.5 |
Use constexpr for values computable at compile time |
See references/code-examples.md § Constants & Immutability for examples.
Concurrency & Parallelism (CP.*)
| Rule |
Summary |
| CP.2 |
Avoid data races |
| CP.3 |
Minimize explicit sharing of writable data |
| CP.4 |
Think in terms of tasks, rather than threads |
| CP.8 |
Avoid using volatile for synchronization |
| CP.20 |
Use RAII, not plain lock()/unlock() |
| CP.21 |
Use std::scoped_lock to acquire multiple mutexes |
| CP.22 |
Avoid calling unknown code while holding a lock |
| CP.42 |
Wait with a condition, not unconditionally |
| CP.44 |
Name your lock_guards and unique_locks |
| CP.100 |
Prefer higher-level concurrency over lock-free programming unless profiling demands it |
Anti-patterns to avoid:
volatile for synchronization (CP.8 — it's for hardware I/O only)
- Detaching threads (CP.26 — lifetime management becomes nearly impossible)
- Unnamed lock guards:
std::lock_guard<std::mutex>(m); destroys immediately (CP.44)
- Holding locks while calling callbacks (CP.22 — deadlock risk)
- Lock-free programming without deep expertise (CP.100)
See references/code-examples.md § Concurrency for thread-safe queue and scoped_lock examples.
Templates & Generic Programming (T.*)
| Rule |
Summary |
| T.1 |
Use templates to raise the level of abstraction |
| T.2 |
Use templates to express algorithms for many argument types |
| T.10 |
Specify concepts for all template arguments |
| T.11 |
Use standard concepts whenever possible |
| T.13 |
Prefer shorthand notation for simple concepts |
| T.43 |
Prefer using over typedef |
| T.120 |
Use template metaprogramming only when you really need to |
| T.144 |
Overload function templates instead of specializing them |
Anti-patterns to avoid:
- Unconstrained templates in visible namespaces (T.47)
- Specializing function templates instead of overloading (T.144)
- Template metaprogramming where
constexpr suffices (T.120)
typedef instead of using (T.43)
See references/code-examples.md § Templates for C++20 concepts examples.
Standard Library (SL.*)
| Rule |
Summary |
| SL.1 |
Use libraries wherever possible |
| SL.2 |
Prefer the standard library to other libraries |
| SL.con.1 |
Prefer std::array or std::vector over C arrays |
| SL.con.2 |
Prefer std::vector by default |
| SL.str.1 |
Use std::string to own character sequences |
| SL.str.2 |
Use std::string_view to refer to character sequences |
| SL.io.50 |
Avoid endl (use '\n' — endl forces a flush) |
Enumerations (Enum.*)
| Rule |
Summary |
| Enum.1 |
Prefer enumerations over macros |
| Enum.3 |
Prefer enum class over plain enum |
| Enum.5 |
Avoid ALL_CAPS for enumerators |
| Enum.6 |
Avoid unnamed enumerations |
Source Files & Naming (SF., NL.)
| Rule |
Summary |
| SF.1 |
Use .cpp for code files and .h for interface files |
| SF.7 |
Avoid writing using namespace at global scope in a header |
| SF.8 |
Use #include guards for all .h files |
| SF.11 |
Header files should be self-contained |
| NL.5 |
Avoid encoding type information in names (no Hungarian notation) |
| NL.8 |
Use a consistent naming style |
| NL.9 |
Use ALL_CAPS for macro names only |
| NL.10 |
Prefer underscore_style names |
See references/code-examples.md § Source Files & Naming for header guard and naming convention examples.
Performance (Per.*)
| Rule |
Summary |
| Per.1 |
Avoid optimizing without reason |
| Per.2 |
Avoid optimizing prematurely |
| Per.6 |
Avoid making claims about performance without measurements |
| Per.7 |
Design to enable optimization |
| Per.10 |
Rely on the static type system |
| Per.11 |
Move computation from run time to compile time |
| Per.19 |
Access memory predictably |
Anti-patterns to avoid:
- Optimizing without profiling data (Per.1, Per.6)
- Choosing "clever" low-level code over clear abstractions (Per.4, Per.5)
- Ignoring data layout and cache behavior (Per.19)
Quick Reference Checklist
Before marking C++ work complete:
1---2name: cpp-coding-standards3description: C++ coding standards based on the C++ Core Guidelines (isocpp.github.io). Use when writing, reviewing, or refactoring C++ code to enforce modern, safe, and idiomatic practices.4---56# C++ Coding Standards (C++ Core Guidelines)78Coding standards for modern C++ (C++17/20/23) derived from the [C++ Core Guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines). Enforces type safety, resource safety, immutability, and clarity.910## When to Use1112- Writing new C++ code (classes, functions, templates)13- Reviewing or refactoring existing C++ code14- Making architectural decisions in C++ projects15- Enforcing consistent style across a C++ codebase16- Choosing between language features (e.g., `enum` vs `enum class`, raw pointer vs smart pointer)1718### When Not to Use1920- Non-C++ projects21- Legacy C codebases that cannot adopt modern C++ features22- Embedded/bare-metal contexts where specific guidelines conflict with hardware constraints (adapt selectively)2324## Cross-Cutting Principles25261. **RAII everywhere** (P.8, R.1, E.6, CP.20): Bind resource lifetime to object lifetime272. **Immutability by default** (P.10, Con.1-5, ES.25): Start with `const`/`constexpr`; mutability is the exception283. **Type safety** (P.4, I.4, ES.46-49, Enum.3): Use the type system to prevent errors at compile time294. **Express intent** (P.3, F.1, NL.1-2, T.10): Names, types, and concepts should communicate purpose305. **Minimize complexity** (F.2-3, ES.5, Per.4-5): Simple code is correct code316. **Value semantics over pointer semantics** (C.10, R.3-5, F.20, CP.31): Prefer returning by value and scoped objects3233## Philosophy & Interfaces (P.*, I.*)3435| Rule | Summary |36|------|---------|37| **P.1** | Express ideas directly in code |38| **P.3** | Express intent |39| **P.4** | Ideally, a program should be statically type safe |40| **P.5** | Prefer compile-time checking to run-time checking |41| **P.8** | Avoid leaking any resources |42| **P.10** | Prefer immutable data to mutable data |43| **I.1** | Make interfaces explicit |44| **I.2** | Avoid non-const global variables |45| **I.4** | Make interfaces precisely and strongly typed |46| **I.11** | Transfer ownership via smart pointers, not raw pointers or references |47| **I.23** | Keep the number of function arguments low |4849See `references/code-examples.md` § Philosophy & Interfaces for examples.5051## Functions (F.*)5253| Rule | Summary |54|------|---------|55| **F.1** | Package meaningful operations as carefully named functions |56| **F.2** | A function should perform a single logical operation |57| **F.3** | Keep functions short and simple |58| **F.4** | If a function might be evaluated at compile time, declare it `constexpr` |59| **F.6** | If your function must not throw, declare it `noexcept` |60| **F.8** | Prefer pure functions |61| **F.16** | For "in" parameters, pass cheaply-copied types by value and others by `const&` |62| **F.20** | For "out" values, prefer return values to output parameters |63| **F.21** | To return multiple "out" values, prefer returning a struct |64| **F.43** | Avoid returning a pointer or reference to a local object |6566Anti-patterns to avoid:67- Returning `T&&` from functions (F.45)68- Using `va_arg` / C-style variadics (F.55)69- Capturing by reference in lambdas passed to other threads (F.53)70- Returning `const T` which inhibits move semantics (F.49)7172See `references/code-examples.md` § Functions for parameter passing and constexpr examples.7374## Classes & Class Hierarchies (C.*)7576| Rule | Summary |77|------|---------|78| **C.2** | Use `class` if invariant exists; `struct` if data members vary independently |79| **C.9** | Minimize exposure of members |80| **C.20** | If you can avoid defining default operations, do (Rule of Zero) |81| **C.21** | If you define or `=delete` any copy/move/destructor, handle them all (Rule of Five) |82| **C.35** | Base class destructor: public virtual or protected non-virtual |83| **C.41** | A constructor should create a fully initialized object |84| **C.46** | Declare single-argument constructors `explicit` |85| **C.67** | A polymorphic class should suppress public copy/move |86| **C.128** | Virtual functions: specify exactly one of `virtual`, `override`, or `final` |8788Anti-patterns to avoid:89- Calling virtual functions in constructors/destructors (C.82)90- Using `memset`/`memcpy` on non-trivial types (C.90)91- Providing different default arguments for virtual function and overrider (C.140)92- Making data members `const` or references, which suppresses move/copy (C.12)9394See `references/code-examples.md` § Classes for Rule of Zero, Rule of Five, and class hierarchy examples.9596## Resource Management (R.*)9798| Rule | Summary |99|------|---------|100| **R.1** | Manage resources automatically using RAII |101| **R.3** | A raw pointer (`T*`) is non-owning |102| **R.5** | Prefer scoped objects; avoid heap-allocating unnecessarily |103| **R.10** | Avoid `malloc()`/`free()` |104| **R.11** | Avoid calling `new` and `delete` explicitly |105| **R.20** | Use `unique_ptr` or `shared_ptr` to represent ownership |106| **R.21** | Prefer `unique_ptr` over `shared_ptr` unless sharing ownership |107| **R.22** | Use `make_shared()` to make `shared_ptr`s |108109Anti-patterns to avoid:110- Naked `new`/`delete` (R.11)111- `malloc()`/`free()` in C++ code (R.10)112- Multiple resource allocations in a single expression (R.13 — exception safety hazard)113- `shared_ptr` where `unique_ptr` suffices (R.21)114115See `references/code-examples.md` § Resource Management for smart pointer and RAII examples.116117## Expressions & Statements (ES.*)118119| Rule | Summary |120|------|---------|121| **ES.5** | Keep scopes small |122| **ES.20** | Always initialize an object |123| **ES.23** | Prefer `{}` initializer syntax |124| **ES.25** | Declare objects `const` or `constexpr` unless modification is intended |125| **ES.28** | Use lambdas for complex initialization of `const` variables |126| **ES.45** | Avoid magic constants; use symbolic constants |127| **ES.46** | Avoid narrowing/lossy arithmetic conversions |128| **ES.47** | Use `nullptr` rather than `0` or `NULL` |129| **ES.48** | Avoid casts |130| **ES.50** | Avoid casting away `const` |131132Anti-patterns to avoid:133- Uninitialized variables (ES.20)134- Using `0` or `NULL` as pointer (ES.47 — use `nullptr`)135- C-style casts (ES.48 — use `static_cast`, `const_cast`, etc.)136- Casting away `const` (ES.50)137- Magic numbers without named constants (ES.45)138- Mixing signed and unsigned arithmetic (ES.100)139- Reusing names in nested scopes (ES.12)140141See `references/code-examples.md` § Expressions & Statements for initialization examples.142143## Error Handling (E.*)144145| Rule | Summary |146|------|---------|147| **E.1** | Develop an error-handling strategy early in a design |148| **E.2** | Throw an exception to signal that a function can't perform its assigned task |149| **E.6** | Use RAII to prevent leaks |150| **E.12** | Use `noexcept` when throwing is impossible or unacceptable |151| **E.14** | Use purpose-designed user-defined types as exceptions |152| **E.15** | Throw by value, catch by reference |153| **E.16** | Destructors, deallocation, and swap must not fail |154| **E.17** | Avoid trying to catch every exception in every function |155156Anti-patterns to avoid:157- Throwing built-in types like `int` or string literals (E.14)158- Catching by value (slicing risk) (E.15)159- Empty catch blocks that silently swallow errors160- Using exceptions for flow control (E.3)161- Error handling based on global state like `errno` (E.28)162163See `references/code-examples.md` § Error Handling for exception hierarchy examples.164165## Constants & Immutability (Con.*)166167| Rule | Summary |168|------|---------|169| **Con.1** | By default, make objects immutable |170| **Con.2** | By default, make member functions `const` |171| **Con.3** | By default, pass pointers and references to `const` |172| **Con.4** | Use `const` for values that don't change after construction |173| **Con.5** | Use `constexpr` for values computable at compile time |174175See `references/code-examples.md` § Constants & Immutability for examples.176177## Concurrency & Parallelism (CP.*)178179| Rule | Summary |180|------|---------|181| **CP.2** | Avoid data races |182| **CP.3** | Minimize explicit sharing of writable data |183| **CP.4** | Think in terms of tasks, rather than threads |184| **CP.8** | Avoid using `volatile` for synchronization |185| **CP.20** | Use RAII, not plain `lock()`/`unlock()` |186| **CP.21** | Use `std::scoped_lock` to acquire multiple mutexes |187| **CP.22** | Avoid calling unknown code while holding a lock |188| **CP.42** | Wait with a condition, not unconditionally |189| **CP.44** | Name your `lock_guard`s and `unique_lock`s |190| **CP.100** | Prefer higher-level concurrency over lock-free programming unless profiling demands it |191192Anti-patterns to avoid:193- `volatile` for synchronization (CP.8 — it's for hardware I/O only)194- Detaching threads (CP.26 — lifetime management becomes nearly impossible)195- Unnamed lock guards: `std::lock_guard<std::mutex>(m);` destroys immediately (CP.44)196- Holding locks while calling callbacks (CP.22 — deadlock risk)197- Lock-free programming without deep expertise (CP.100)198199See `references/code-examples.md` § Concurrency for thread-safe queue and scoped_lock examples.200201## Templates & Generic Programming (T.*)202203| Rule | Summary |204|------|---------|205| **T.1** | Use templates to raise the level of abstraction |206| **T.2** | Use templates to express algorithms for many argument types |207| **T.10** | Specify concepts for all template arguments |208| **T.11** | Use standard concepts whenever possible |209| **T.13** | Prefer shorthand notation for simple concepts |210| **T.43** | Prefer `using` over `typedef` |211| **T.120** | Use template metaprogramming only when you really need to |212| **T.144** | Overload function templates instead of specializing them |213214Anti-patterns to avoid:215- Unconstrained templates in visible namespaces (T.47)216- Specializing function templates instead of overloading (T.144)217- Template metaprogramming where `constexpr` suffices (T.120)218- `typedef` instead of `using` (T.43)219220See `references/code-examples.md` § Templates for C++20 concepts examples.221222## Standard Library (SL.*)223224| Rule | Summary |225|------|---------|226| **SL.1** | Use libraries wherever possible |227| **SL.2** | Prefer the standard library to other libraries |228| **SL.con.1** | Prefer `std::array` or `std::vector` over C arrays |229| **SL.con.2** | Prefer `std::vector` by default |230| **SL.str.1** | Use `std::string` to own character sequences |231| **SL.str.2** | Use `std::string_view` to refer to character sequences |232| **SL.io.50** | Avoid `endl` (use `'\n'` — `endl` forces a flush) |233234## Enumerations (Enum.*)235236| Rule | Summary |237|------|---------|238| **Enum.1** | Prefer enumerations over macros |239| **Enum.3** | Prefer `enum class` over plain `enum` |240| **Enum.5** | Avoid ALL_CAPS for enumerators |241| **Enum.6** | Avoid unnamed enumerations |242243## Source Files & Naming (SF.*, NL.*)244245| Rule | Summary |246|------|---------|247| **SF.1** | Use `.cpp` for code files and `.h` for interface files |248| **SF.7** | Avoid writing `using namespace` at global scope in a header |249| **SF.8** | Use `#include` guards for all `.h` files |250| **SF.11** | Header files should be self-contained |251| **NL.5** | Avoid encoding type information in names (no Hungarian notation) |252| **NL.8** | Use a consistent naming style |253| **NL.9** | Use ALL_CAPS for macro names only |254| **NL.10** | Prefer `underscore_style` names |255256See `references/code-examples.md` § Source Files & Naming for header guard and naming convention examples.257258## Performance (Per.*)259260| Rule | Summary |261|------|---------|262| **Per.1** | Avoid optimizing without reason |263| **Per.2** | Avoid optimizing prematurely |264| **Per.6** | Avoid making claims about performance without measurements |265| **Per.7** | Design to enable optimization |266| **Per.10** | Rely on the static type system |267| **Per.11** | Move computation from run time to compile time |268| **Per.19** | Access memory predictably |269270Anti-patterns to avoid:271- Optimizing without profiling data (Per.1, Per.6)272- Choosing "clever" low-level code over clear abstractions (Per.4, Per.5)273- Ignoring data layout and cache behavior (Per.19)274275## Quick Reference Checklist276277Before marking C++ work complete:278279- [ ] No raw `new`/`delete` — use smart pointers or RAII (R.11)280- [ ] Objects initialized at declaration (ES.20)281- [ ] Variables are `const`/`constexpr` by default (Con.1, ES.25)282- [ ] Member functions are `const` where possible (Con.2)283- [ ] `enum class` instead of plain `enum` (Enum.3)284- [ ] `nullptr` instead of `0`/`NULL` (ES.47)285- [ ] No narrowing conversions (ES.46)286- [ ] No C-style casts (ES.48)287- [ ] Single-argument constructors are `explicit` (C.46)288- [ ] Rule of Zero or Rule of Five applied (C.20, C.21)289- [ ] Base class destructors are public virtual or protected non-virtual (C.35)290- [ ] Templates are constrained with concepts (T.10)291- [ ] No `using namespace` in headers at global scope (SF.7)292- [ ] Headers have include guards and are self-contained (SF.8, SF.11)293- [ ] Locks use RAII (`scoped_lock`/`lock_guard`) (CP.20)294- [ ] Exceptions are custom types, thrown by value, caught by reference (E.14, E.15)295- [ ] `'\n'` instead of `std::endl` (SL.io.50)296- [ ] No magic numbers (ES.45)