2026-02-20 01:11:30 -08:00
---
name : cpp-coding-standards
description : 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.
2026-06-11 21:12:21 +09:00
metadata :
origin : ECC
2026-02-20 01:11:30 -08:00
---
# C++ Coding Standards (C++ Core Guidelines)
Comprehensive coding 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.
## 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
These themes recur across the entire guidelines and form the foundation:
1. **RAII everywhere** (P.8, R.1, E.6, CP.20): Bind resource lifetime to object lifetime
2. **Immutability by default** (P.10, Con.1-5, ES.25): Start with `const` /`constexpr` ; mutability is the exception
3. **Type safety** (P.4, I.4, ES.46-49, Enum.3): Use the type system to prevent errors at compile time
4. **Express intent** (P.3, F.1, NL.1-2, T.10): Names, types, and concepts should communicate purpose
5. **Minimize complexity** (F.2-3, ES.5, Per.4-5): Simple code is correct code
6. **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.*)
### Key Rules
| 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** | Don't leak 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** | Never transfer ownership by a raw pointer or reference |
| **I.23** | Keep the number of function arguments low |
### DO
```cpp
// P.10 + I.4: Immutable, strongly typed interface
struct Temperature {
double kelvin ;
};
Temperature boil ( const Temperature & water );
```
### DON'T
```cpp
// Weak interface: unclear ownership, unclear units
double boil ( double * temp );
// Non-const global variable
int g_counter = 0 ; // I.2 violation
```
## Functions (F.*)
### Key Rules
| 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** | Never return a pointer or reference to a local object |
### Parameter Passing
```cpp
// F.16: Cheap types by value, others by const&
void print ( int x ); // cheap: by value
void analyze ( const std :: string & data ); // expensive: by const&
void transform ( std :: string s ); // sink: by value (will move)
// F.20 + F.21: Return values, not output parameters
struct ParseResult {
std :: string token ;
int position ;
};
ParseResult parse ( std :: string_view input ); // GOOD: return struct
// BAD: output parameters
void parse ( std :: string_view input ,
std :: string & token , int & pos ); // avoid this
```
### Pure Functions and constexpr
```cpp
// F.4 + F.8: Pure, constexpr where possible
constexpr int factorial ( int n ) noexcept {
return ( n <= 1 ) ? 1 : n * factorial ( n - 1 );
}
static_assert ( factorial ( 5 ) == 120 );
```
### Anti-Patterns
- 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)
## Classes & Class Hierarchies (C.*)
### Key Rules
| 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` |
### Rule of Zero
```cpp
// C.20: Let the compiler generate special members
struct Employee {
std :: string name ;
std :: string department ;
int id ;
// No destructor, copy/move constructors, or assignment operators needed
};
```
### Rule of Five
```cpp
// C.21: If you must manage a resource, define all five
class Buffer {
public :
explicit Buffer ( std :: size_t size )
: data_ ( std :: make_unique < char [] > ( size )), size_ ( size ) {}
~ Buffer () = default ;
Buffer ( const Buffer & other )
: data_ ( std :: make_unique < char [] > ( other . size_ )), size_ ( other . size_ ) {
std :: copy_n ( other . data_ . get (), size_ , data_ . get ());
}
Buffer & operator = ( const Buffer & other ) {
if ( this != & other ) {
auto new_data = std :: make_unique < char [] > ( other . size_ );
std :: copy_n ( other . data_ . get (), other . size_ , new_data . get ());
data_ = std :: move ( new_data );
size_ = other . size_ ;
}
return * this ;
}
Buffer ( Buffer && ) noexcept = default ;
Buffer & operator = ( Buffer && ) noexcept = default ;
private :
std :: unique_ptr < char [] > data_ ;
std :: size_t size_ ;
};
```
### Class Hierarchy
```cpp
// C.35 + C.128: Virtual destructor, use override
class Shape {
public :
virtual ~ Shape () = default ;
virtual double area () const = 0 ; // C.121: pure interface
};
class Circle : public Shape {
public :
explicit Circle ( double r ) : radius_ ( r ) {}
double area () const override { return 3.14159 * radius_ * radius_ ; }
private :
double radius_ ;
};
```
### Anti-Patterns
- 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)
## Resource Management (R.*)
### Key Rules
| Rule | Summary |
|------|---------|
| **R.1** | Manage resources automatically using RAII |
| **R.3** | A raw pointer (`T*` ) is non-owning |
| **R.5** | Prefer scoped objects; don't heap-allocate 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_ptr` s |
### Smart Pointer Usage
```cpp
// R.11 + R.20 + R.21: RAII with smart pointers
auto widget = std :: make_unique < Widget > ( "config" ); // unique ownership
auto cache = std :: make_shared < Cache > ( 1024 ); // shared ownership
// R.3: Raw pointer = non-owning observer
void render ( const Widget * w ) { // does NOT own w
if ( w ) w -> draw ();
}
render ( widget . get ());
```
### RAII Pattern
```cpp
// R.1: Resource acquisition is initialization
class FileHandle {
public :
explicit FileHandle ( const std :: string & path )
: handle_ ( std :: fopen ( path . c_str (), "r" )) {
if ( ! handle_ ) throw std :: runtime_error ( "Failed to open: " + path );
}
~ FileHandle () {
if ( handle_ ) std :: fclose ( handle_ );
}
FileHandle ( const FileHandle & ) = delete ;
FileHandle & operator = ( const FileHandle & ) = delete ;
FileHandle ( FileHandle && other ) noexcept
: handle_ ( std :: exchange ( other . handle_ , nullptr )) {}
FileHandle & operator = ( FileHandle && other ) noexcept {
if ( this != & other ) {
if ( handle_ ) std :: fclose ( handle_ );
handle_ = std :: exchange ( other . handle_ , nullptr );
}
return * this ;
}
private :
std :: FILE * handle_ ;
};
```
### Anti-Patterns
- 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)
## Expressions & Statements (ES.*)
### Key Rules
| 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** | Don't cast away `const` |
### Initialization
```cpp
// ES.20 + ES.23 + ES.25: Always initialize, prefer {}, default to const
const int max_retries { 3 };
const std :: string name { "widget" };
const std :: vector < int > primes { 2 , 3 , 5 , 7 , 11 };
// ES.28: Lambda for complex const initialization
const auto config = [ & ] {
Config c ;
c . timeout = std :: chrono :: seconds { 30 };
c . retries = max_retries ;
c . verbose = debug_mode ;
return c ;
}();
```
### Anti-Patterns
- 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)
## Error Handling (E.*)
### Key Rules
| 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 never fail |
| **E.17** | Don't try to catch every exception in every function |
### Exception Hierarchy
```cpp
// E.14 + E.15: Custom exception types, throw by value, catch by reference
class AppError : public std :: runtime_error {
public :
using std :: runtime_error :: runtime_error ;
};
class NetworkError : public AppError {
public :
NetworkError ( const std :: string & msg , int code )
: AppError ( msg ), status_code ( code ) {}
int status_code ;
};
void fetch_data ( const std :: string & url ) {
// E.2: Throw to signal failure
throw NetworkError ( "connection refused" , 503 );
}
void run () {
try {
fetch_data ( "https://api.example.com" );
} catch ( const NetworkError & e ) {
log_error ( e . what (), e . status_code );
} catch ( const AppError & e ) {
log_error ( e . what ());
}
// E.17: Don't catch everything here -- let unexpected errors propagate
}
```
### Anti-Patterns
- 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)
## Constants & Immutability (Con.*)
### All Rules
| 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 |
```cpp
// Con.1 through Con.5: Immutability by default
class Sensor {
public :
explicit Sensor ( std :: string id ) : id_ ( std :: move ( id )) {}
// Con.2: const member functions by default
const std :: string & id () const { return id_ ; }
double last_reading () const { return reading_ ; }
// Only non-const when mutation is required
void record ( double value ) { reading_ = value ; }
private :
const std :: string id_ ; // Con.4: never changes after construction
double reading_ { 0.0 };
};
// Con.3: Pass by const reference
void display ( const Sensor & s ) {
std :: cout << s . id () << ": " << s . last_reading () << '\n' ;
}
// Con.5: Compile-time constants
constexpr double PI = 3.14159265358979 ;
constexpr int MAX_SENSORS = 256 ;
```
## Concurrency & Parallelism (CP.*)
### Key Rules
| 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** | Don't use `volatile` for synchronization |
| **CP.20** | Use RAII, never plain `lock()` /`unlock()` |
| **CP.21** | Use `std::scoped_lock` to acquire multiple mutexes |
| **CP.22** | Never call unknown code while holding a lock |
| **CP.42** | Don't wait without a condition |
| **CP.44** | Remember to name your `lock_guard` s and `unique_lock` s |
| **CP.100** | Don't use lock-free programming unless you absolutely have to |
### Safe Locking
```cpp
// CP.20 + CP.44: RAII locks, always named
class ThreadSafeQueue {
public :
void push ( int value ) {
std :: lock_guard < std :: mutex > lock ( mutex_ ); // CP.44: named!
queue_ . push ( value );
cv_ . notify_one ();
}
int pop () {
std :: unique_lock < std :: mutex > lock ( mutex_ );
// CP.42: Always wait with a condition
cv_ . wait ( lock , [ this ] { return ! queue_ . empty (); });
const int value = queue_ . front ();
queue_ . pop ();
return value ;
}
private :
std :: mutex mutex_ ; // CP.50: mutex with its data
std :: condition_variable cv_ ;
std :: queue < int > queue_ ;
};
```
### Multiple Mutexes
```cpp
// CP.21: std::scoped_lock for multiple mutexes (deadlock-free)
void transfer ( Account & from , Account & to , double amount ) {
std :: scoped_lock lock ( from . mutex_ , to . mutex_ );
from . balance_ -= amount ;
to . balance_ += amount ;
}
```
### Anti-Patterns
- `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)
## Templates & Generic Programming (T.*)
### Key Rules
| 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** | Don't specialize function templates (overload instead) |
### Concepts (C++20)
```cpp
#include <concepts>
// T.10 + T.11: Constrain templates with standard concepts
template < std :: integral T >
T gcd ( T a , T b ) {
while ( b != 0 ) {
a = std :: exchange ( b , a % b );
}
return a ;
}
// T.13: Shorthand concept syntax
void sort ( std :: ranges :: random_access_range auto & range ) {
std :: ranges :: sort ( range );
}
// Custom concept for domain-specific constraints
template < typename T >
concept Serializable = requires ( const T & t ) {
{ t . serialize () } -> std :: convertible_to < std :: string > ;
};
template < Serializable T >
void save ( const T & obj , const std :: string & path );
```
### Anti-Patterns
- 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)
## Standard Library (SL.*)
### Key Rules
| 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) |
```cpp
// SL.con.1 + SL.con.2: Prefer vector/array over C arrays
const std :: array < int , 4 > fixed_data { 1 , 2 , 3 , 4 };
std :: vector < std :: string > dynamic_data ;
// SL.str.1 + SL.str.2: string owns, string_view observes
std :: string build_greeting ( std :: string_view name ) {
return "Hello, " + std :: string ( name ) + "!" ;
}
// SL.io.50: Use '\n' not endl
std :: cout << "result: " << value << '\n' ;
```
## Enumerations (Enum.*)
### Key Rules
| Rule | Summary |
|------|---------|
| **Enum.1** | Prefer enumerations over macros |
| **Enum.3** | Prefer `enum class` over plain `enum` |
| **Enum.5** | Don't use ALL_CAPS for enumerators |
| **Enum.6** | Avoid unnamed enumerations |
```cpp
// Enum.3 + Enum.5: Scoped enum, no ALL_CAPS
enum class Color { red , green , blue };
enum class LogLevel { debug , info , warning , error };
// BAD: plain enum leaks names, ALL_CAPS clashes with macros
enum { RED , GREEN , BLUE }; // Enum.3 + Enum.5 + Enum.6 violation
#define MAX_SIZE 100 // Enum.1 violation -- use constexpr
```
## Source Files & Naming (SF.*, NL.*)
### Key Rules
| Rule | Summary |
|------|---------|
| **SF.1** | Use `.cpp` for code files and `.h` for interface files |
| **SF.7** | Don't write `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 |
### Header Guard
```cpp
// SF.8: Include guard (or #pragma once)
#ifndef PROJECT_MODULE_WIDGET_H
#define PROJECT_MODULE_WIDGET_H
// SF.11: Self-contained -- include everything this header needs
#include <string>
#include <vector>
namespace project :: module {
class Widget {
public :
explicit Widget ( std :: string name );
const std :: string & name () const ;
private :
std :: string name_ ;
};
} // namespace project::module
#endif // PROJECT_MODULE_WIDGET_H
```
### Naming Conventions
```cpp
// NL.8 + NL.10: Consistent underscore_style
namespace my_project {
constexpr int max_buffer_size = 4096 ; // NL.9: not ALL_CAPS (it's not a macro)
class tcp_connection { // underscore_style class
public :
void send_message ( std :: string_view msg );
bool is_connected () const ;
private :
std :: string host_ ; // trailing underscore for members
int port_ ;
};
} // namespace my_project
```
### Anti-Patterns
- `using namespace std;` in a header at global scope (SF.7)
- Headers that depend on inclusion order (SF.10, SF.11)
- Hungarian notation like `strName` , `iCount` (NL.5)
- ALL_CAPS for anything other than macros (NL.9)
## Performance (Per.*)
### Key Rules
| Rule | Summary |
|------|---------|
| **Per.1** | Don't optimize without reason |
| **Per.2** | Don't optimize prematurely |
| **Per.6** | Don't make 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 |
### Guidelines
```cpp
// Per.11: Compile-time computation where possible
constexpr auto lookup_table = [] {
std :: array < int , 256 > table {};
for ( int i = 0 ; i < 256 ; ++ i ) {
table [ i ] = i * i ;
}
return table ;
}();
// Per.19: Prefer contiguous data for cache-friendliness
std :: vector < Point > points ; // GOOD: contiguous
std :: vector < std :: unique_ptr < Point >> indirect_points ; // BAD: pointer chasing
```
### Anti-Patterns
- 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:
- [ ] No raw `new` /`delete` -- use smart pointers or RAII (R.11)
- [ ] Objects initialized at declaration (ES.20)
- [ ] Variables are `const` /`constexpr` by default (Con.1, ES.25)
- [ ] Member functions are `const` where possible (Con.2)
- [ ] `enum class` instead of plain `enum` (Enum.3)
- [ ] `nullptr` instead of `0` /`NULL` (ES.47)
- [ ] No narrowing conversions (ES.46)
- [ ] No C-style casts (ES.48)
- [ ] Single-argument constructors are `explicit` (C.46)
- [ ] Rule of Zero or Rule of Five applied (C.20, C.21)
- [ ] Base class destructors are public virtual or protected non-virtual (C.35)
- [ ] Templates are constrained with concepts (T.10)
- [ ] No `using namespace` in headers at global scope (SF.7)
- [ ] Headers have include guards and are self-contained (SF.8, SF.11)
- [ ] Locks use RAII (`scoped_lock` /`lock_guard` ) (CP.20)
- [ ] Exceptions are custom types, thrown by value, caught by reference (E.14, E.15)
- [ ] `'\n'` instead of `std::endl` (SL.io.50)
- [ ] No magic numbers (ES.45)