| name | C++ Core Guidelines Summary |
|---|---|
| description | Exhaustive actionable summary of the ISO C++ Core Guidelines (Stroustrup & Sutter) — covers philosophy, interfaces, functions, classes, enums, resources, expressions, performance, concurrency, errors, constants, templates, C-interop, source files, stdlib, and naming |
| type | reference |
This is a summary of C++ Core Guidelines. The original book is available here: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines If rule is unclear you may reference the original, but avoid doing so if you can get by otherwise. A summary of these rules should be available in project-scoped memory.
- P.1: Use typed return values and const semantics; let compilers verify intent rather than relying on comments
- P.2: Write standard ISO C++; avoid non-standard extensions to ensure portability
- P.3: Use named loops (range-for, algorithms) and strongly-typed parameters instead of raw indices and generic types
- P.4: Minimize type-safety violations: avoid unions (use variant), casts, array decay, range errors, and narrowing conversions
- P.5: Use compile-time checks (
static_assert, type system) instead of runtime checks when possible - P.6: Design APIs so errors can be checked at runtime; avoid designs that make error detection infeasible
- P.7: Validate inputs early with assertions and bounds checking to catch errors before corrupting state
- P.8: Don't leak any resources — use RAII, smart pointers, and deterministic cleanup
- P.9: Don't waste time or space — write efficient code by default, optimize only after measurement
- P.10: Prefer
constandconstexprdata over mutable state to prevent unexpected changes and enable optimization - P.11: Encapsulate low-level unsafe operations in well-designed interfaces rather than spreading them throughout code
- P.12: Use static analyzers, sanitizers, and testing tools to catch errors automatically
- P.13: Use standard and well-tested libraries instead of writing new implementations from scratch
- I.1: State intent explicitly in the function signature; never rely on global variables or hidden dependencies
- I.2: Avoid non-const global variables; they hide dependencies and make behavior unpredictable
- I.3: Avoid singletons; use immutable global constants or dependency injection instead
- I.4: Use strongly-typed parameters (e.g.,
Speed sinstead ofdouble s) to prevent misuse and enable verification - I.5: Document preconditions in comments or code; state what values are valid inputs
- I.6: Use
Expects()macro to declare and check preconditions at function entry - I.7: Document postconditions; state what the function guarantees after successful return
- I.8: Use
Ensures()macro to declare and check postconditions before function return - I.9: Document template parameters with
requiresclauses to specify exact type requirements - I.10: Throw exceptions for contract failures; never return error codes that callers can ignore
- I.11: Use smart pointers (
unique_ptr,shared_ptr) for ownership transfer, never raw pointers - I.12: Mark non-nullable pointers as
not_null<T*>to enable static analysis and optimization - I.13: Pass arrays as
span<T>with bounds, never as bare pointers (T* p, int n) - I.22: Initialize global objects simply; avoid complex initialization that creates order-of-initialization bugs
- I.23: Limit function arguments to 4-5; abstract multiple related parameters into a struct
- I.24: Avoid adjacent same-typed parameters (e.g.,
copy_n(T* from, T* to)); useconstto distinguish - I.25: Use empty abstract base classes as interfaces; avoid storing state in base classes
- I.26: For cross-compiler ABI stability, use C-style subset without virtual functions or STL
- I.27: Use Pimpl (pointer-to-implementation) for library interfaces to hide implementation details
- I.30: Encapsulate unsafe patterns locally; never expose them in public interfaces
- F.1: Name and extract cohesive operations into functions to improve reusability and reduce errors
- F.2: Each function should perform one logical task; split multi-purpose functions into separate functions
- F.3: Keep functions short (fit on screen); avoid nested complexity and multiple responsibilities
- F.4: Declare functions
constexprif they might be evaluated at compile time - F.5: Mark very small, time-critical functions as
inlineto encourage compiler inlining - F.6: Mark functions that never throw exceptions as
noexceptfor optimization and safety - F.7: Accept raw pointers or references, not smart pointers, for parameters that don't manage ownership
- F.8: Prefer pure functions (same input always produces same output, no side effects)
- F.9: Leave unused parameters unnamed to suppress compiler warnings and clarify intentionality
- F.10: Name reusable operations; avoid duplicating the same logic in multiple places
- F.11: Use unnamed lambdas for short, single-use function objects defined inline
- F.15: Use conventional parameter passing (by value, by
const&, by&, by&&) not clever tricks - F.16: For "in" parameters, pass small types by value and large types by
const& - F.17: For "in-out" parameters that will be modified, pass by non-const reference
- F.18: For "will-move-from" parameters, pass by
X&&and usestd::moveto transfer ownership - F.19: For "forward" template parameters, pass by
TP&&and usestd::forwardonly - F.20: Return output values directly, not via output parameters; use structured bindings in C++17
- F.21: Return multiple values as a named struct, not via output parameters
- F.22: Use
T*orowner<T*>to designate a single object; be explicit about ownership - F.23: Use
not_null<T>to document that a pointer parameter must never be null - F.24: Use
span<T>to pass array ranges with bounds; never use(T* p, int n) - F.25: Use
zstringornot_null<zstring>for C-style null-terminated string parameters - F.26: Use
unique_ptr<T>to transfer exclusive ownership through pointers - F.27: Use
shared_ptr<T>when multiple owners must manage an object's lifetime - F.42: Return
T*only to indicate a position/element in a container (search results), never for ownership - F.43: Never return pointers or references to local variables; they become dangling after return
- F.44: Return
T&when avoiding a copy matters and "no object" is not an option - F.45: Never return
T&&; temporary objects go out of scope immediately, creating dangling references - F.46:
main()must returnint(notvoid) for C++ standard compliance - F.47: Assignment operators must return non-const
T&to chain assignments - F.48: Don't
std::move()local returns; let RVO (Return Value Optimization) handle it - F.49: Don't return
const T; it prevents move semantics and offers no benefit - F.50: Use lambdas to capture local variables or define local functions; use functions for overloading
- F.51: Prefer default arguments over overloading to reduce code duplication
- F.52: Capture by reference in lambdas used locally (passed to local algorithms, not returned)
- F.53: Capture by value in lambdas that outlive the scope (returned, stored, passed to threads)
- F.54: Don't use
[=]in member functions (capturesthisby value confusingly); writethisexplicitly - F.55: Never use
va_argor variadic functions; use templates or overloading for type safety - F.56: Return early from functions for exceptional cases; avoid deeply nested conditions
- F.60: Use
T*when null is valid; useT&when "no argument" is not an option
- C.1: Group logically related data together using structs or classes for clarity
- C.2: Use
classwhen enforcing an invariant; usestructfor independent data members - C.3: Use a class to clearly distinguish between public interface and private implementation
- C.4: Make a function a member only if it needs direct access to private class data
- C.5: Define helper functions in the same namespace as the class they support for argument-dependent lookup
- C.7: Separate type definition from variable declaration; don't combine them in one statement
- C.8: Use
classinstead ofstructwhen any member is non-public - C.9: Make members as private as possible; expose only what clients need
- C.10: Prefer concrete types over inheritance hierarchies for simplicity and efficiency
- C.11: Make concrete types regular: support equality comparison and assignment with consistent semantics
- C.12: Don't use
constor reference data members in copyable or movable types
- C.20: Rely on compiler-generated default operations when possible; avoid explicit implementations
- C.21: If you define or
=deleteany of copy, move, or destructor, define or=deleteall five special members (Rule of Five) - C.22: Ensure copy, move, and destructor operations maintain consistent semantics with each other
- C.30: Define a destructor only when your class manages resources not handled by member destructors
- C.31: Release all resources acquired by a class in its destructor
- C.32: Clarify ownership: use
owner<T*>or smart pointers for owning pointers, not bareT* - C.33: Define a destructor for classes with owning pointer members
- C.35: Base class destructors must be either public-virtual or protected-nonvirtual
- C.36: Destructors must never throw exceptions
- C.37: Mark destructors
noexceptto prevent unexpected exceptions during cleanup - C.40: Define a constructor to establish the class invariant
- C.41: Constructors must create fully initialized objects; don't require post-construction initialization
- C.42: If constructor cannot create a valid object, throw an exception, not return an invalid state
- C.43: Copyable classes must have a default constructor for container support
- C.44: Default constructors should be simple, fast, and non-throwing
- C.45: Use in-class initializers instead of constructor-based initialization for constant defaults
- C.46: Declare single-argument constructors
explicitby default to prevent unintended conversions - C.47: Initialize data members in declaration order, matching the order in member initializer lists
- C.48: Use default member initializers for constant values instead of repeating in each constructor
- C.49: Initialize members in the member initializer list, not via assignment in constructor body
- C.50: Use factory functions when virtual behavior is needed during object initialization
- C.51: Use delegating constructors to avoid repeating initialization logic across multiple constructors
- C.52: Use
usingto inherit constructors from base classes when derived classes don't need additional initialization - C.60: Make copy assignment non-virtual, take
const¶meter, return non-const reference - C.61: Copy operations must produce equivalent objects; after
a=b,aandbshould compare equal - C.62: Handle self-assignment safely in copy assignment (e.g.,
swaptechnique avoids explicit self-check) - C.63: Make move assignment non-virtual, take
&¶meter, return non-const reference - C.64: Move operations must transfer ownership and leave source in a valid, usable state
- C.65: Handle self-assignment in move operations to prevent double-deletion of resources
- C.66: Declare move operations
noexceptfor efficient use by standard library - C.67: Polymorphic classes should suppress public copy/move to prevent slicing; use
=deleteorclone() - C.80: Use
=defaultwhen explicitly stating you want compiler-generated special member semantics - C.81: Use
=deletewhen you need to disable a default operation without providing an alternative - C.82: Never call virtual functions from constructors or destructors; use factory functions instead
- C.83: Provide a
noexceptswap function for value-like types to enable efficient assignment - C.84: Swap functions must never fail; they are used in contexts assuming nothrow semantics
- C.85: Mark swap as
noexceptto guarantee it won't throw exceptions - C.86: Overload
==as non-member,noexcept, and symmetric in both operands and types - C.87: Avoid virtual
operator==in base classes; comparison hierarchies are complex and error-prone - C.89: Overload
hashasnoexcept; standard containers require exception-free hashing - C.90: Use constructors and assignment operators for initialization/copying, not
memset/memcpy
- C.100: Follow STL design patterns when defining containers for familiarity and consistency
- C.101: Design containers with value semantics so copies are independent
- C.102: Provide move operations for containers to avoid expensive copying
- C.103: Give containers an initializer list constructor for convenient initialization
- C.104: Default-construct containers as empty for regularity and vector support
- C.109: Provide
*and->operators for resource handles with pointer semantics
- C.120: Use class hierarchies only to represent naturally hierarchical domain concepts
- C.121: Pure abstract base classes should have only pure virtual functions and no data
- C.122: Use abstract base classes to completely separate interface from implementation
- C.126: Abstract classes typically don't need user-defined constructors
- C.127: Virtual functions require either public-virtual or protected-nonvirtual destructors
- C.128: Virtual functions must use exactly one of
virtual,override, orfinalkeywords - C.129: Distinguish between implementation inheritance (reusing code) and interface inheritance (contracts)
- C.130: Provide virtual
clone()functions for deep copying polymorphic objects, not public copy operations - C.131: Avoid getters/setters that merely return/set a member; use public data or add behavior instead
- C.132: Don't declare functions
virtualwithout a reason (e.g., base class for inheritance) - C.133: Avoid
protecteddata; use private data with accessors or public data without invariants - C.134: Non-const data members should have uniform access level (all public or all private)
- C.135: Use multiple inheritance to combine multiple distinct interfaces in one class
- C.136: Use multiple inheritance to combine implementation attributes and optional behaviors
- C.137: Use virtual bases to allow separate inheritance hierarchies for interface and implementation
- C.138: Use
usingdeclarations to bring overloaded base functions into derived class scope - C.139: Use
finalsparingly on classes; it prevents valuable future extensions - C.140: Don't give virtual functions different default arguments than their overrides
- C.145: Access polymorphic objects through pointers/references, not by value, to prevent slicing
- C.146: Use
dynamic_castwhen navigating a class hierarchy; prefer virtual functions - C.147: Use
dynamic_castto reference when failure means an error; it throws on failure - C.148: Use
dynamic_castto pointer when failure is acceptable; it returns null on failure - C.149: Use
unique_ptrorshared_ptrfor dynamically allocated objects to prevent leaks - C.150: Use
make_unique()to construct objects owned byunique_ptr - C.151: Use
make_shared()to construct objects owned byshared_ptr - C.152: Never assign a derived array pointer to a base class pointer; it breaks pointer arithmetic
- C.153: Prefer virtual function calls to type casting for safe, correct runtime polymorphism
- C.160: Define operators primarily to mimic conventional usage
- C.161: Use non-member functions for symmetric operators
- C.162: Overload operations that are roughly equivalent
- C.163: Overload only for operations that are roughly equivalent
- C.164: Avoid implicit conversion operators
- C.165: Use
usingfor customization points - C.166: Overload unary
&only as part of a system of smart pointers and references - C.167: Use an operator for an operation with its conventional meaning
- C.168: Define overloaded operators in the namespace of their operands
- C.170: If you feel like overloading a lambda, use a generic lambda
- C.180: Use unions to save memory when only one member is active at a time
- C.181: Avoid "naked" unions; wrap them in a class that tracks the active member
- C.182: Use anonymous unions to implement tagged unions with a discriminant field
- C.183: Don't use a union for type punning; use
memcpyorbit_castinstead
- Enum.1: Use type-safe enums instead of macros for named constant groups with proper scoping
- Enum.2: Use enumerations to group related named constants as a logically coherent set
- Enum.3: Prefer
enum classto prevent implicit int conversions and namespace pollution - Enum.4: Define operators (e.g., increment) on enums to enable safe manipulation patterns
- Enum.5: Use lowercase names for enumerators to prevent macro naming clashes
- Enum.6: Give all enumerations a name; unnamed enums indicate logically unrelated values
- Enum.7: Specify underlying type only for space savings, C compatibility, or bit-precision needs
- Enum.8: Omit explicit values unless required; compiler-generated sequences are simpler and safer
- R.1: Use RAII (constructor/destructor pairing) via resource handles to prevent leaks automatically
- R.2: In interfaces, use raw pointers only for single objects; use
spanor containers for arrays - R.3: Raw pointers (
T*) are non-owning by default; use smart pointers orowner<T*>for ownership - R.4: Raw references (
T&) are non-owning; use smart pointers when ownership is required - R.5: Prefer stack-allocated scoped objects to heap allocation unless memory constraints force otherwise
- R.6: Avoid mutable global variables; use const globals or local variables instead
- R.10: Avoid
malloc()andfree(); usenew/deleteor smart pointers for proper construction/destruction - R.11: Avoid explicit
newanddeletecalls in application code; usemake_uniqueor smart pointers - R.12: Immediately assign allocation results to smart pointers to prevent leaks if exceptions occur
- R.13: Perform only one explicit allocation per statement to avoid unspecified evaluation order leaks
- R.14: Replace raw
[]parameters withgsl::spanto preserve size information for range checking - R.15: Always provide matching
operator newandoperator deletepairs to prevent heap corruption - R.20: Use
unique_ptrorshared_ptrto explicitly document and enforce ownership semantics - R.21: Prefer
unique_ptrovershared_ptrunless multiple owners actually share the object - R.22: Use
make_shared()instead ofnewto combine allocations and ensure exception safety - R.23: Use
make_unique()instead ofnewto avoid type repetition and ensure exception safety - R.24: Use
std::weak_ptrto break reference cycles inshared_ptrgraphs - R.30: Accept smart pointers as parameters only to express lifetime semantics, not for passing objects
- R.31: Follow standard smart pointer patterns (
copyablefor shared, non-copyable for unique) - R.32: Accept
unique_ptr<T>parameter to signal that the function takes ownership of the object - R.33: Accept
unique_ptr<T>¶meter to signal the function may reseat (reassign) the pointer - R.34: Accept
shared_ptr<T>parameter to explicitly signal shared ownership participation - R.35: Accept
shared_ptr<T>¶meter to signal the function may reseat the shared pointer - R.36: Accept
const shared_ptr<T>&to signal the function might retain a reference to the object - R.37: Never pass raw pointers/references from aliased smart pointers; create local copies instead
- ES.1: Use standard library functions over third-party or hand-written implementations
- ES.2: Use library/class abstractions instead of low-level language features directly
- ES.3: Eliminate code duplication via refactoring or standard algorithms; avoid cut-and-paste
- ES.5: Declare variables in the smallest scope needed; minimize lifetime and resource retention
- ES.6: Declare loop variables in
for-statement initializers to limit their scope - ES.7: Use short names for common/local variables; longer names for uncommon/non-local ones
- ES.8: Avoid visually similar names (e.g.,
l,O,I) that cause confusion - ES.9: Avoid
ALL_CAPSnames for non-macros; reserveALL_CAPSfor preprocessor directives - ES.10: Declare one variable per declaration statement to improve readability
- ES.11: Use
autoto avoid redundant type name repetition in variable declarations - ES.12: Do not reuse names from outer scopes in inner scopes; causes accidental shadowing bugs
- ES.20: Always initialize objects at declaration; uninitialized variables are error-prone
- ES.21: Don't introduce variables until you need them; delay declaration until first use
- ES.22: Declare variables only when you have a value to initialize them with
- ES.23: Use brace-initializer
{}syntax instead of=for safer, narrowing-aware initialization - ES.24: Use
unique_ptr<T>for pointer members instead of raw pointers to enforce cleanup - ES.25: Declare objects
constorconstexprunless you plan to mutate them later - ES.26: Don't reuse a variable for unrelated purposes; use separate variables for clarity
- ES.27: Use
std::arrayorstack_arrayinstead of C-style arrays for type-safety - ES.28: Use lambdas for complex initialization logic, especially for
constvariables
- ES.30: Don't use macros for code text manipulation; use inline functions or templates instead
- ES.31: Don't use macros for constants or function-like behavior; use
constor inline functions - ES.32: Use
ALL_CAPSfor all macro names to identify them as unsafe preprocessor directives - ES.33: Give all macros unique names with prefixes to avoid accidental conflicts
- ES.34: Don't define C-style variadic functions; use templates, overloads, or initializer lists
- ES.40: Avoid complex expressions; break multi-step calculations into named subexpressions
- ES.41: Add parentheses if operator precedence is unclear to prevent logic errors
- ES.42: Keep pointer arithmetic minimal and explicit; prefer iterators or ranges instead
- ES.43: Avoid expressions with unspecified evaluation order; assign subexpressions to variables
- ES.44: Don't depend on function argument evaluation order; it is unspecified in C++
- ES.45: Replace magic numbers with named symbolic constants for maintainability
- ES.46: Avoid narrowing conversions (e.g.,
doubletoint) that lose precision - ES.47: Use
nullptrinstead of0orNULLfor null pointer constants - ES.48: Avoid casts; they disable type safety — redesign if casts seem necessary
- ES.49: If a cast is unavoidable, use named casts (
static_cast,const_cast) not C-style casts - ES.50: Never cast away
const; if needed, redesign to respect const-correctness - ES.55: Use containers or
spanwith bounds information instead of raw pointers requiring checks - ES.56: Write
std::move()only when explicitly moving to another scope; avoid over-using it - ES.60: Avoid
newanddeletein application code; use stack variables or smart pointers - ES.61: Use
delete[]for arrays,deletefor single objects; mismatching causes corruption - ES.62: Don't compare pointers from different arrays; order comparison is undefined
- ES.63: Never slice objects in containers or assignments; causes loss of derived-class state
- ES.64: Use
T{initializer}constructor syntax for consistency and clarity - ES.65: Never dereference invalid pointers; ensure pointers reference live objects
- ES.70: Use
switchoverif-chains when testing a single value against multiple cases - ES.71: Use range-
forloops over traditionalforloops unless explicit index manipulation is needed - ES.72: Use
forloops with explicit loop variables instead ofwhilewhen iteration count is clear - ES.73: Use
whileloops when there is no obvious loop variable or termination count - ES.74: Declare loop variables in
for-statement initializers, not before the loop - ES.75: Avoid
do-whileloops;whileandforexpress intent more clearly - ES.76: Avoid
gotostatements; use loops, conditionals, or exceptions for control flow - ES.77: Minimize
breakandcontinuein loops; refactor complex loops for clarity - ES.78: Don't rely on implicit fallthrough in
switchcases; use explicitbreakor comment - ES.79: Use
defaultcase only for common cases; exhaustive casing is safer - ES.84: Don't declare unnamed local variables; all variables should have meaningful names
- ES.85: Make empty statements visible with
{}or comment; single;can hide logic errors - ES.86: Avoid modifying loop control variables inside raw
for-loop bodies; causes surprises - ES.87: Don't add redundant
==or!=to conditions; writeif (flag)notif (flag == true)
- ES.100: Don't mix signed and unsigned arithmetic; cast explicitly to make intent clear
- ES.101: Use
unsignedtypes for bit manipulation, bitwise operations, and flags - ES.102: Use
signedtypes for numeric arithmetic to avoid unexpected wraparound behavior - ES.103: Don't overflow; check bounds before arithmetic or use overflow-safe types
- ES.104: Don't underflow; check bounds before subtraction or use types that prevent it
- ES.105: Never divide by integer zero; check divisor before division
- ES.106: Don't use
unsignedto represent non-negative values; useintor check bounds explicitly - ES.107: Use
gsl::indexinstead ofunsignedfor array subscripts for safety
- Per.1: Only optimize code if you have a concrete need, not out of habit or curiosity
- Per.2: Optimize only after profiling shows a real performance bottleneck
- Per.3: Focus optimization effort on code paths consuming the most execution time via profiling
- Per.4: Simple code often optimizes better than complex code; let optimizers do their job
- Per.5: High-level code can outperform low-level code that inhibits compiler optimizations
- Per.6: Support performance claims with actual measurements using profilers or benchmarks
- Per.7: Design interfaces to pass sufficient information enabling efficient implementations
- Per.10: Use strong static typing to eliminate
void*and weak types for better compiler optimization - Per.11: Use
constexprto move computations from runtime to compile time for speed and safety - Per.12: Remove redundant aliases that inhibit compiler alias analysis and optimization
- Per.13: Eliminate redundant pointer indirections that prevent inlining and optimization
- Per.14: Batch allocations and deallocations; avoid frequent
malloc/freecalls in tight loops - Per.15: Avoid dynamic allocations (
new/delete) on critical code paths; use stack or pools - Per.16: Use compact, contiguous data structures; memory access dominates performance
- Per.17: Declare most-frequently-accessed struct members first for better cache utilization
- Per.18: Minimize memory footprint; smaller data fits in cache, reducing access latency
- Per.19: Access memory sequentially and linearly for optimal cache performance; avoid random access
- Per.30: Avoid thread context switches in latency-critical code paths
- CP.1: Design libraries and reusable code to be thread-safe; assume multithreaded use
- CP.2: Use synchronization (mutexes, atomics) to prevent concurrent read-write data access
- CP.3: Minimize shared writable state; use immutable data and message passing instead
- CP.4: Think in terms of tasks and high-level abstractions, not low-level threads and locks
- CP.8: Don't use
volatilefor synchronization; usestd::atomicor mutex instead - CP.9: Use static checkers (Clang TSA), dynamic tools (ThreadSanitizer), or code review to catch races
- CP.20: Use RAII for locks (
scoped_lock), never plainlock()/unlock() - CP.21: Use
std::lock()orstd::scoped_lockto acquire multiple mutexes without deadlock - CP.22: Never call unknown code while holding a lock (e.g., a callback)
- CP.23: Think of a joining thread as a scoped container for concurrent work
- CP.24: Think of a thread as a global container; it doesn't own local references after detach
- CP.25: Prefer
gsl::joining_threadoverstd::threadfor automatic join on scope exit - CP.26: Don't
detach()a thread; it makes lifetime management and error handling impossible - CP.31: Pass small amounts of data between threads by value, not by reference or pointer
- CP.32: To share ownership between unrelated threads use
shared_ptr - CP.40: Minimize context switching by reducing lock contention and thread hand-offs
- CP.41: Minimize thread creation and destruction; use thread pools for repeated work
- CP.42: Don't wait without a condition; always use condition variables with a predicate
- CP.43: Minimize time spent in a critical section; do expensive work outside the lock
- CP.44: Name your
lock_guards andunique_locks; unnamed temporaries unlock immediately - CP.50: Define a mutex together with the data it guards; use
synchronized_value<T>where possible
- CP.51: Do not use capturing lambdas that are coroutines; captured references may dangle after suspension
- CP.52: Do not hold locks or other synchronization primitives across suspension points
- CP.53: Parameters to coroutines should not be passed by reference; they may dangle after suspension
- CP.60: Use a future to return a value from a concurrent task
- CP.61: Use
async()to spawn concurrent tasks with automatic thread management
- CP.100: Don't use lock-free programming unless you absolutely have to; it's subtle and error-prone
- CP.101: Distrust your hardware/compiler combination; verify lock-free code with stress tests and formal analysis
- CP.102: Carefully study the literature before attempting lock-free data structures
- CP.110: Do not write your own double-checked locking for initialization; use
std::call_onceor static locals - CP.111: Use a conventional pattern if you really need double-checked locking
- CP.200: Use
volatileonly to talk to non-C++ memory (memory-mapped I/O, signal handlers) - CP.201: ??? Signals
- E.1: Plan error handling strategy early in design; retrofitting is costly and incomplete
- E.2: Throw exceptions to signal when functions cannot fulfill their contracts
- E.3: Use exceptions only for errors, not control flow; exceptions are rare-case optimized
- E.4: Design recovery around maintaining class invariants; invalid state causes undefined behavior
- E.5: Let constructors establish invariants and throw if initialization fails
- E.6: Use RAII with resource handles to auto-cleanup on scope exit, preventing leaks
- E.7: Document function preconditions to avoid interface violations and undefined behavior
- E.8: Document function postconditions to clarify what callers can expect on success
- E.12: Mark functions
noexceptwhen throwing is impossible or unacceptable (failures are fatal) - E.13: Never throw while directly owning raw resources (pointers); use smart pointers or RAII
- E.14: Throw user-defined exception types, not built-in types or
std::exceptiondirectly - E.15: Throw by value; catch by
constreference to avoid slicing and copying - E.16: Destructors, deallocation, swap, and exception copy/move must never throw or fail
- E.17: Don't catch exceptions in functions that can't meaningfully recover; let them propagate
- E.18: Minimize try/catch blocks; prefer RAII and exception propagation to direct handlers
- E.19: Use
gsl::finallyfor cleanup when no suitable resource handle class exists - E.25: If exceptions unavailable, simulate RAII with
valid()checks on every resource - E.26: If exceptions unavailable, call
abort()on unrecoverable errors to prevent corruption - E.27: If exceptions unavailable, use error codes systematically (check after every operation)
- E.28: Avoid error-checking via global state (
errno); use return values or exceptions - E.30: Don't use exception specifications (
throw(X, Y)); they're fragile and removed from standard - E.31: Order catch clauses from most-derived to base types;
catch (...)hides all later clauses
- Con.1: Use
constby default for objects that don't change after creation - Con.2: Mark member functions
constunless they modify the object's observable state - Con.3: Pass pointers and references to
constby default to prevent unintended modification - Con.4: Apply
constto objects with values that don't change after construction - Con.5: Use
constexprfor values computable at compile time for better performance and safety
- T.1: Use templates to abstract operations over multiple types for reusability and efficiency
- T.2: Use templates for algorithms applicable to many argument types to minimize code duplication
- T.3: Use templates for containers to express element type generically and type-safely
- T.4: Use templates to express syntax tree manipulation
- T.5: Combine templates and OO techniques to leverage static polymorphism with dynamic interfaces
- T.10: Specify concepts for all template arguments to enable proper type constraints and error checking
- T.11: Use standard library concepts instead of inventing custom ones for consistency and correctness
- T.12: Prefer concept names over
autoin local variables for clarity and semantic meaning - T.13: Use shorthand concept syntax (
Concept TorConcept auto) instead of verboserequiresfor readability - T.20: Define concepts with meaningful semantics, not just syntax (e.g., Number must follow mathematical laws)
- T.21: Require a complete set of operations in concepts to prevent accidental type matches
- T.22: Specify axioms for concepts to document expected mathematical or behavioral properties
- T.23: Add new use patterns to differentiate refined concepts from general ones
- T.24: Use tag classes or traits to distinguish concepts differing only in semantics, not syntax
- T.25: Avoid complementary constraints; use positive constraints instead
- T.26: Define concepts via use-patterns and actual operations rather than bare syntactic requirements
- T.30: Use concept negation sparingly to express a minor difference
- T.31: Use concept disjunction sparingly to express alternatives
- T.40: Pass operations to algorithms via function objects for customization and efficiency
- T.41: Require only essential properties in template concepts; avoid over-constraining
- T.42: Use template aliases to simplify notation and hide implementation details
- T.43: Prefer
usingaliases overtypedeffor better readability and template support - T.44: Use function templates to deduce class template argument types automatically where feasible
- T.46: Require template arguments to be at least semiregular (default-constructible, copyable)
- T.47: Avoid highly visible unconstrained templates with common names to prevent accidental instantiation
- T.48: Use
enable_ifto fake concepts when compiler doesn't support C++20 concepts - T.49: Avoid type-erasure where possible; prefer templates for compile-time type information
- T.60: Minimize a template's dependencies on other templates and types for clarity
- T.61: Don't over-parameterize class members; use non-template base classes for shared code (SCARY)
- T.62: Place non-dependent class template members in a non-templated base class for code reuse
- T.64: Use specialization to provide optimized implementations of class templates for specific types
- T.65: Use tag dispatch to provide alternative function implementations based on type properties
- T.67: Use specialization for irregular types that don't fit the generic algorithm
- T.68: Use
{}instead of()in templates to avoid ambiguity with function declarations - T.69: Don't make unqualified non-member function calls in templates unless intentional customization points
- T.80: Don't naively templatize a class hierarchy; use virtual functions or separate templates
- T.81: Don't mix class hierarchies with arrays of objects from those hierarchies
- T.82: Linearize a class hierarchy when virtual functions are undesirable for performance
- T.83: Don't declare member function templates as virtual (not supported in C++)
- T.84: Use a non-template core implementation to provide ABI stability while enabling template interfaces
- T.100: Use variadic templates for functions taking variable numbers of arguments of different types
- T.101: How to pass arguments to a variadic template
- T.102: Process variadic template arguments sequentially through recursion or fold expressions
- T.103: Don't use variadic templates for homogeneous argument lists; use
std::vectoror similar
- T.120: Use template metaprogramming only when necessary; prefer
constexprfunctions when possible - T.121: Use TMP primarily to emulate concepts and define type relationships before C++20 concepts
- T.122: Use template aliases and
std::enable_ifto compute types at compile time - T.123: Use
constexprfunctions to compute values at compile time instead of TMP - T.124: Prefer standard-library TMP facilities like
<type_traits>over custom implementations - T.125: Use existing TMP libraries beyond standard library when needed; don't reinvent
- T.140: Give reusable operations a name instead of inlining them everywhere
- T.141: Use unnamed lambdas for simple function objects used in one place only
- T.142: Use template variables to simplify notation and reduce redundancy
- T.143: Don't write unintentionally non-generic code; use generic patterns consistently
- T.144: Don't specialize function templates; use overloads or tag dispatch instead
- T.150: Use
static_assertto verify at compile time that a class matches a concept
- CPL.1: Prefer C++ to C for better type checking, safety, and expressiveness
- CPL.2: When using C code, use the C/C++ common subset and compile as C++ for better type checking
- CPL.3: Wrap C interfaces in C++ abstractions; use C++ for calling code
- SF.1: Use
.cppsuffix for implementation files and.hfor header files unless project convention differs - SF.2: Headers must not contain object or non-inline function definitions; only declarations and inline code
- SF.3: Place all declarations used in multiple source files in header files for maintainability
- SF.4: Include all header files before other declarations to catch missing dependencies early
- SF.5: Implementation files must
#includetheir interface headers for compile-time consistency checking - SF.6: Use
using namespacefor transition, foundation libraries (std), or local scope only - SF.7: Never write
using namespaceat global scope in headers; prevents caller disambiguation - SF.8: Use
#includeguards on all headers with unique, library-qualified names to prevent redefinition - SF.9: Avoid cyclic dependencies between source files; redesign to break cycles cleanly
- SF.10: Avoid undeclared dependencies on names brought in by indirect includes
- SF.11: Make header files self-contained by including all dependencies they need
- SF.12: Use quotes for local includes and angle brackets for standard/external includes
- SF.13: Use portable header identifiers: lowercase matching official names and forward slashes for paths
- SF.20: Use namespaces to express logical program structure and prevent name clashes
- SF.21: Never use unnamed namespaces in headers; they violate one-definition rule
- SF.22: Use unnamed namespaces in implementation files for internal/non-exported entities
- SL.1: Use libraries wherever possible to avoid reinventing wheels
- SL.2: Prefer the C++ standard library over other libraries for stability and availability
- SL.3: Never add non-standard entities to namespace
std; use your own namespaces - SL.4: Use the standard library safely: respect type constraints and avoid unsafe patterns
- SL.con.1: Prefer
std::arrayorstd::vectorover C arrays for bounds safety and size tracking - SL.con.2: Use
std::vectorby default for mutable collections;std::arrayfor fixed sizes - SL.con.3: Avoid out-of-bounds access; use
.at()for bounds-checked access or range-for loops - SL.con.4: Don't use
memset/memcpyon non-trivially-copyable types; use copy constructors - SL.str.1: Use
std::stringto own character sequences; provides allocation, copying, and operations - SL.str.2: Use
std::string_view(read-only) orgsl::span<char>(read-write) for non-owning references - SL.str.3: Use
zstringorczstringto indicate C-style zero-terminated strings vs. genericchar* - SL.str.4: Use
char*only for single-character pointers; clarify intent with types likezstring - SL.str.5: Use
std::bytefor byte values that don't represent characters - SL.str.10: Use
std::stringfor locale-sensitive string operations to enable locale facilities - SL.str.11: Use
gsl::span<char>when you need to mutate strings;std::string_viewis read-only - SL.str.12: Use string literal suffix
sto createstd::stringdirectly instead of C-style strings - SL.io.1: Use character-level input only when necessary; prefer formatted input (
operator >>) - SL.io.2: Validate input during reading; handle ill-formed input immediately
- SL.io.3: Prefer
iostreamfor type-safe, extensible I/O over C-styleprintf - SL.io.10: Call
ios_base::sync_with_stdio(false)to avoid costly synchronization if not mixing with printf - SL.io.50: Avoid
endl; use'\n'instead to prevent redundant flush operations - SL.C.1: Don't use
setjmp/longjmp; they bypass destructors and invalidate RAII
- NL.1: Express intent in code itself; avoid restating code logic in comments
- NL.2: State intent in comments; explain what code is supposed to do, not what it does
- NL.3: Keep comments brief; verbosity decreases readability
- NL.4: Maintain consistent indentation style; use a tool to enforce automatically
- NL.5: Avoid encoding type information in names; rely on function overloading and type deduction
- NL.7: Make name length proportional to scope size; longer names for wider scopes
- NL.8: Use consistent naming style throughout the project
- NL.9: Use
ALL_CAPSonly for macro names to distinguish them from normal identifiers - NL.10: Prefer
underscore_stylenames following ISO standard and C++ standard library convention - NL.11: Make numeric literals readable using digit separators (
') and literal suffixes - NL.15: Use spaces sparingly around operators and brackets to reduce visual clutter
- NL.16: Declare class members in order: types, constructors/destructors, functions, then data
- NL.17: Use K&R-derived layout: opening brace with class/struct, separate line for function brace
- NL.18: Use C++-style declarator layout (
T¬T &) to emphasize type rather than usage - NL.19: Avoid names easily confused (e.g.,
oO01lL); ensure visual clarity - NL.20: Don't place two statements on one line; each statement on its own line
- NL.21: Declare one name per declaration statement to avoid confusion with declarator syntax
- NL.25: Don't use
voidas function argument type in C++; use empty parameter list - NL.26: Use conventional
constnotation (const T, notT const) for consistency - NL.27: Use
.cppfor implementation and.hfor headers