Skip to content

Instantly share code, notes, and snippets.

@Caellian
Created April 1, 2026 17:24
Show Gist options
  • Select an option

  • Save Caellian/be0eef84b1a5261ea3b9f1dcca209489 to your computer and use it in GitHub Desktop.

Select an option

Save Caellian/be0eef84b1a5261ea3b9f1dcca209489 to your computer and use it in GitHub Desktop.
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: Philosophy

  • 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 const and constexpr data 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: Interfaces

  • 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 s instead of double 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 requires clauses 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)); use const to 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: Functions

  • 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 constexpr if they might be evaluated at compile time
  • F.5: Mark very small, time-critical functions as inline to encourage compiler inlining
  • F.6: Mark functions that never throw exceptions as noexcept for 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 use std::move to transfer ownership
  • F.19: For "forward" template parameters, pass by TP&& and use std::forward only
  • 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* or owner<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 zstring or not_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 return int (not void) 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 (captures this by value confusingly); write this explicitly
  • F.55: Never use va_arg or 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; use T& when "no argument" is not an option

C: Classes and Class Hierarchies

General

  • C.1: Group logically related data together using structs or classes for clarity
  • C.2: Use class when enforcing an invariant; use struct for 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 class instead of struct when any member is non-public
  • C.9: Make members as private as possible; expose only what clients need

Concrete types

  • 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 const or reference data members in copyable or movable types

Constructors, assignments, and destructors

  • C.20: Rely on compiler-generated default operations when possible; avoid explicit implementations
  • C.21: If you define or =delete any of copy, move, or destructor, define or =delete all 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 bare T*
  • 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 noexcept to 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 explicit by 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 using to inherit constructors from base classes when derived classes don't need additional initialization
  • C.60: Make copy assignment non-virtual, take const& parameter, return non-const reference
  • C.61: Copy operations must produce equivalent objects; after a=b, a and b should compare equal
  • C.62: Handle self-assignment safely in copy assignment (e.g., swap technique avoids explicit self-check)
  • C.63: Make move assignment non-virtual, take && parameter, 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 noexcept for efficient use by standard library
  • C.67: Polymorphic classes should suppress public copy/move to prevent slicing; use =delete or clone()
  • C.80: Use =default when explicitly stating you want compiler-generated special member semantics
  • C.81: Use =delete when 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 noexcept swap 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 noexcept to 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 hash as noexcept; standard containers require exception-free hashing
  • C.90: Use constructors and assignment operators for initialization/copying, not memset/memcpy

Containers and resource handles

  • 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

Class hierarchies

  • 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, or final keywords
  • 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 virtual without a reason (e.g., base class for inheritance)
  • C.133: Avoid protected data; 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 using declarations to bring overloaded base functions into derived class scope
  • C.139: Use final sparingly 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_cast when navigating a class hierarchy; prefer virtual functions
  • C.147: Use dynamic_cast to reference when failure means an error; it throws on failure
  • C.148: Use dynamic_cast to pointer when failure is acceptable; it returns null on failure
  • C.149: Use unique_ptr or shared_ptr for dynamically allocated objects to prevent leaks
  • C.150: Use make_unique() to construct objects owned by unique_ptr
  • C.151: Use make_shared() to construct objects owned by shared_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

Overloading and overloaded operators

  • 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 using for 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

Unions

  • 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 memcpy or bit_cast instead

Enum: Enumerations

  • 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 class to 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: Resource Management

  • 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 span or containers for arrays
  • R.3: Raw pointers (T*) are non-owning by default; use smart pointers or owner<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() and free(); use new/delete or smart pointers for proper construction/destruction
  • R.11: Avoid explicit new and delete calls in application code; use make_unique or 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 with gsl::span to preserve size information for range checking
  • R.15: Always provide matching operator new and operator delete pairs to prevent heap corruption
  • R.20: Use unique_ptr or shared_ptr to explicitly document and enforce ownership semantics
  • R.21: Prefer unique_ptr over shared_ptr unless multiple owners actually share the object
  • R.22: Use make_shared() instead of new to combine allocations and ensure exception safety
  • R.23: Use make_unique() instead of new to avoid type repetition and ensure exception safety
  • R.24: Use std::weak_ptr to break reference cycles in shared_ptr graphs
  • R.30: Accept smart pointers as parameters only to express lifetime semantics, not for passing objects
  • R.31: Follow standard smart pointer patterns (copyable for 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>& parameter 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>& parameter 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: Expressions and Statements

Declarations

  • 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_CAPS names for non-macros; reserve ALL_CAPS for preprocessor directives
  • ES.10: Declare one variable per declaration statement to improve readability
  • ES.11: Use auto to 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 const or constexpr unless 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::array or stack_array instead of C-style arrays for type-safety
  • ES.28: Use lambdas for complex initialization logic, especially for const variables

Expressions

  • 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 const or inline functions
  • ES.32: Use ALL_CAPS for 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., double to int) that lose precision
  • ES.47: Use nullptr instead of 0 or NULL for 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 span with 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 new and delete in application code; use stack variables or smart pointers
  • ES.61: Use delete[] for arrays, delete for 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

Statements

  • ES.70: Use switch over if-chains when testing a single value against multiple cases
  • ES.71: Use range-for loops over traditional for loops unless explicit index manipulation is needed
  • ES.72: Use for loops with explicit loop variables instead of while when iteration count is clear
  • ES.73: Use while loops 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-while loops; while and for express intent more clearly
  • ES.76: Avoid goto statements; use loops, conditionals, or exceptions for control flow
  • ES.77: Minimize break and continue in loops; refactor complex loops for clarity
  • ES.78: Don't rely on implicit fallthrough in switch cases; use explicit break or comment
  • ES.79: Use default case 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; write if (flag) not if (flag == true)

Arithmetic

  • ES.100: Don't mix signed and unsigned arithmetic; cast explicitly to make intent clear
  • ES.101: Use unsigned types for bit manipulation, bitwise operations, and flags
  • ES.102: Use signed types 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 unsigned to represent non-negative values; use int or check bounds explicitly
  • ES.107: Use gsl::index instead of unsigned for array subscripts for safety

Per: Performance

  • 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 constexpr to 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/free calls 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: Concurrency and Parallelism

General

  • 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 volatile for synchronization; use std::atomic or mutex instead
  • CP.9: Use static checkers (Clang TSA), dynamic tools (ThreadSanitizer), or code review to catch races

Mutexes and locks

  • CP.20: Use RAII for locks (scoped_lock), never plain lock()/unlock()
  • CP.21: Use std::lock() or std::scoped_lock to 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_thread over std::thread for 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 and unique_locks; unnamed temporaries unlock immediately
  • CP.50: Define a mutex together with the data it guards; use synchronized_value<T> where possible

Coroutines

  • 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

Async and futures

  • 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

Lock-free programming

  • 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_once or static locals
  • CP.111: Use a conventional pattern if you really need double-checked locking

Volatile

  • CP.200: Use volatile only to talk to non-C++ memory (memory-mapped I/O, signal handlers)
  • CP.201: ??? Signals

E: Error Handling

  • 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 noexcept when 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::exception directly
  • E.15: Throw by value; catch by const reference 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::finally for 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: Constants and Immutability

  • Con.1: Use const by default for objects that don't change after creation
  • Con.2: Mark member functions const unless they modify the object's observable state
  • Con.3: Pass pointers and references to const by default to prevent unintended modification
  • Con.4: Apply const to objects with values that don't change after construction
  • Con.5: Use constexpr for values computable at compile time for better performance and safety

T: Templates and Generic Programming

General

  • 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

Concepts

  • 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 auto in local variables for clarity and semantic meaning
  • T.13: Use shorthand concept syntax (Concept T or Concept auto) instead of verbose requires for 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

Template interfaces

  • 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 using aliases over typedef for 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_if to fake concepts when compiler doesn't support C++20 concepts
  • T.49: Avoid type-erasure where possible; prefer templates for compile-time type information

Template definitions

  • 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

Template hierarchies

  • 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

Variadic templates

  • 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::vector or similar

Metaprogramming

  • T.120: Use template metaprogramming only when necessary; prefer constexpr functions 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_if to compute types at compile time
  • T.123: Use constexpr functions 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

Other

  • 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_assert to verify at compile time that a class matches a concept

CPL: C-style Programming

  • 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: Source Files

  • SF.1: Use .cpp suffix for implementation files and .h for 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 #include their interface headers for compile-time consistency checking
  • SF.6: Use using namespace for transition, foundation libraries (std), or local scope only
  • SF.7: Never write using namespace at global scope in headers; prevents caller disambiguation
  • SF.8: Use #include guards 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: The Standard Library

  • 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::array or std::vector over C arrays for bounds safety and size tracking
  • SL.con.2: Use std::vector by default for mutable collections; std::array for 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/memcpy on non-trivially-copyable types; use copy constructors
  • SL.str.1: Use std::string to own character sequences; provides allocation, copying, and operations
  • SL.str.2: Use std::string_view (read-only) or gsl::span<char> (read-write) for non-owning references
  • SL.str.3: Use zstring or czstring to indicate C-style zero-terminated strings vs. generic char*
  • SL.str.4: Use char* only for single-character pointers; clarify intent with types like zstring
  • SL.str.5: Use std::byte for byte values that don't represent characters
  • SL.str.10: Use std::string for locale-sensitive string operations to enable locale facilities
  • SL.str.11: Use gsl::span<char> when you need to mutate strings; std::string_view is read-only
  • SL.str.12: Use string literal suffix s to create std::string directly 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 iostream for type-safe, extensible I/O over C-style printf
  • 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: Naming and Layout

  • 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_CAPS only for macro names to distinguish them from normal identifiers
  • NL.10: Prefer underscore_style names 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& not 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 void as function argument type in C++; use empty parameter list
  • NL.26: Use conventional const notation (const T, not T const) for consistency
  • NL.27: Use .cpp for implementation and .h for headers
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment