C++ Coding Standards (C++ Core Guidelines)
Derived from C++ Core Guidelines comprehensive coding standards for modern C++ (C++17/20/23). Enforce 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 in a C++ codebase
- Choosing between language features (e.g.,
enum vs enum class, raw pointers vs smart pointers)
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 (requires selective adjustment)
Core Principles (Cross-Cutting Principles)
The following themes run throughout the guidelines and form the foundation:
- RAII everywhere (P.8, R.1, E.6, CP.20): Bind resource lifetime to object lifetime. Resource Acquisition Is Initialization (RAII)
- Immutability by default (P.10, Con.1-5, ES.25): Prefer
const/constexpr; mutability should be the exception. Immutability
- Type safety (P.4, I.4, ES.46-49, Enum.3): Use the type system to prevent errors at compile time. Type safety
- Express intent (P.3, F.1, NL.1-2, T.10): Names, types, and concepts should convey purpose. Express intent
- Minimize complexity (F.2-3, ES.5, Per.4-5): Simple code is correct code
- Value semantics over pointer semantics (C.10, R.3-5, F.20, CP.31): Prefer returning by value and scoped objects. Value semantics
Philosophy and interfaces (P., I.)
Core 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 over run-time checking |
| P.8 |
Don't leak any resources |
| P.10 |
Prefer immutable data over 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 |
Correct examples (DO)
// P.10 + I.4: 不可变的强类型接口
struct Temperature {
double kelvin;
};
Temperature boil(const Temperature& water);
Incorrect examples (DON'T)
// 弱接口:所有权不明确,单位不明确
double boil(double* temp);
// 非 const 全局变量
int g_counter = 0; // 违反 I.2
Functions (F.*)
Core 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 may be evaluated at compile time, declare it constexpr |
| F.6 |
If a function never throws an exception, declare it noexcept |
| F.8 |
Prefer pure functions |
| F.16 |
For "input" parameters, pass low-cost copy types by value, and other types by const& Pass |
| F.20 |
For "output" values, prefer return values over output parameters |
| F.21 |
To return multiple "output" values, prefer returning a struct |
| F.43 |
Never return a pointer or reference to a local object |
Parameter passing
// F.16: 低开销类型按值传递,其他按 const& 传递
void print(int x); // 低开销:按值
void analyze(const std::string& data); // 高开销:按 const&
void transform(std::string s); // 接收端:按值(将触发 move)
// F.20 + F.21: 使用返回值,而非输出参数
struct ParseResult {
std::string token;
int position;
};
ParseResult parse(std::string_view input); // 推荐:返回结构体
// 不良实践:使用输出参数
void parse(std::string_view input,
std::string& token, int& pos); // 避免这样做
Pure functions and constexpr
// F.4 + F.8: 尽可能使用纯函数和 constexpr
constexpr int factorial(int n) noexcept {
return (n <= 1) ? 1 : n * factorial(n - 1);
}
static_assert(factorial(5) == 120);
Anti-Patterns
- Returning
T&& (F.45)
- Using
va_arg / C-style variadic arguments (F.55)
- Capturing by reference in a lambda passed to another thread (F.53)
- Returning
const T, which inhibits move semantics (F.49)
Classes and class hierarchies (C.*)
Core rules
| Rule |
Summary |
| C.2 |
If an invariant exists, use class; if member data varies independently, use struct |
| C.9 |
Minimize the public exposure of members |
| C.20 |
If you can avoid defining default operations, do not define them (Rule of Zero) |
| C.21 |
If you define or =delete any copy/move/destructor function, handle all five (Rule of Five) |
| C.35 |
Base class destructor: either public virtual or protected non-virtual |
| C.41 |
A constructor should create a fully initialized object |
| C.46 |
Declare single-argument constructors as explicit |
| C.67 |
A polymorphic class should suppress public copy/move |
| C.128 |
Virtual functions: must specify exactly one of virtual、override or final one of |
Rule of Zero
// C.20: 让编译器生成特殊成员
struct Employee {
std::string name;
std::string department;
int id;
// 无需析构函数、拷贝/移动构造函数或赋值运算符
};
Rule of Five
// C.21: 如果必须管理资源,请定义所有五个
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 hierarchies
// C.35 + C.128: 虚析构函数,使用 override
class Shape {
public:
virtual ~Shape() = default;
virtual double area() const = 0; // C.121: 纯接口
};
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 (C.90)
- Providing different default arguments for virtual functions and their overriders (C.140)
- Making data members
const or references, which inhibits move/copy (C.12)
Resource management (R.*)
Core rules
| Rule |
Summary |
| R.1 |
Use RAII to manage resources automatically |
| R.3 |
Raw pointers (T*) are non-owning |
| R.5 |
Prefer scoped objects; do not allocate on the heap unnecessarily |
| R.10 |
Avoid using malloc()/free() |
| R.11 |
Avoid explicitly calling new and delete |
| R.20 |
Use unique_ptr or shared_ptr to express ownership |
| R.21 |
Unless sharing ownership, prefer unique_ptr over shared_ptr |
| R.22 |
Use make_shared() to create shared_ptr |
Smart pointer usage
// R.11 + R.20 + R.21: 使用智能指针进行 RAII
auto widget = std::make_unique<Widget>("config"); // 独占所有权
auto cache = std::make_shared<Cache>(1024); // 共享所有权
// R.3: 原始指针 = 非所有权的观察者
void render(const Widget* w) { // 不拥有 w
if (w) w->draw();
}
render(widget.get());
RAII pattern
// 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
- Bare
new/delete (R.11)
- Used in C++ code
malloc()/free() (R.10)
- Multiple resource allocations in a single expression (R.13 — exception safety risk)
- In
unique_ptr use shared_ptr (R.21)
Expressions and statements (ES.*)
Core rules
| Rule |
Summary |
| ES.5 |
Keep scopes small |
| ES.20 |
Always initialize objects |
| ES.23 |
Prefer {} initialization syntax |
| ES.25 |
Declare objects as const or constexpr |
| ES.28 |
For const use a lambda for complex initialization of variables |
| ES.45 |
Avoid magic constants; use symbolic constants |
| ES.46 |
Avoid narrowing/lossy arithmetic conversions |
| ES.47 |
Use nullptr instead of 0 or NULL |
| ES.48 |
Avoid casts |
| ES.50 |
Do not cast away const property |
Initialization
// ES.20 + ES.23 + ES.25: 始终初始化,优先使用 {},默认设为 const
const int max_retries{3};
const std::string name{"widget"};
const std::vector<int> primes{2, 3, 5, 7, 11};
// ES.28: 用于复杂 const 初始化的 lambda
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)
- Use
0 or NULL as pointers (ES.47 — should use nullptr)
- C-style casts (ES.48 — should use
static_cast、const_cast etc.)
- Cast away
const property (ES.50)
- Magic numbers without named constants (ES.45)
- Mixed signed and unsigned arithmetic operations (ES.100)
- Reusing names in nested scopes (ES.12)
Error handling (E.*)
Core rules
| Rules |
Summary |
| E.1 |
Develop an error-handling strategy early in design |
| E.2 |
Throw an exception when a function cannot perform its assigned task |
| E.6 |
Use RAII to prevent leaks |
| E.12 |
Use when throwing exceptions is impossible or unacceptable noexcept |
| E.14 |
Use specially designed user-defined types as exceptions |
| E.15 |
Throw by value, catch by reference |
| E.16 |
Destructors, deallocation operations, and swap must never fail |
| E.17 |
Do not try to catch every exception in every function |
Exception hierarchy
// E.14 + E.15: 自定义异常类型,按值抛出,按引用捕获
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 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: 不要在此处捕获所有异常 —— 让非预期错误继续传播
}
Anti-Patterns
- Throwing built-in types such as
int or string literals (E.14)
- Catching by value (risk of object slicing) (E.15)
- Empty catch blocks that silently swallow errors
- Using exceptions for control flow (E.3)
- Error handling based on global state (such as
errno) (E.28)
Constants and immutability (Con.*)
All rules
| Rules |
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 pointers and references |
| Con.4 |
Use for values that do not change after construction const |
| Con.5 |
Use for values computable at compile time constexpr |
// Con.1 到 Con.5: 默认不变性
class Sensor {
public:
explicit Sensor(std::string id) : id_(std::move(id)) {}
// Con.2: 默认 const 成员函数
const std::string& id() const { return id_; }
double last_reading() const { return reading_; }
// 仅在需要修改时才使用非 const
void record(double value) { reading_ = value; }
private:
const std::string id_; // Con.4: 构造后绝不改变
double reading_{0.0};
};
// Con.3: 按 const 引用传递
void display(const Sensor& s) {
std::cout << s.id() << ": " << s.last_reading() << '\n';
}
// Con.5: 编译时常量
constexpr double PI = 3.14159265358979;
constexpr int MAX_SENSORS = 256;
Concurrency and parallelism (CP.*)
Core rules
| Rules |
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 |
Do not use volatile for synchronization |
| CP.20 |
Use RAII, never call directly lock()/unlock() |
| CP.21 |
Use std::scoped_lock Acquire multiple mutexes |
| CP.22 |
Never call unknown code while holding a lock |
| CP.42 |
Don't wait unconditionally |
| CP.44 |
Remember to name your lock_guard and unique_lock Naming |
| CP.100 |
Don't use lock-free programming unless absolutely necessary |
Safe locking
// CP.20 + CP.44: RAII 锁,始终命名
class ThreadSafeQueue {
public:
void push(int value) {
std::lock_guard<std::mutex> lock(mutex_); // CP.44: 已命名!
queue_.push(value);
cv_.notify_one();
}
int pop() {
std::unique_lock<std::mutex> lock(mutex_);
// CP.42: 始终带条件等待
cv_.wait(lock, [this] { return !queue_.empty(); });
const int value = queue_.front();
queue_.pop();
return value;
}
private:
std::mutex mutex_; // CP.50: 互斥锁及其保护的数据
std::condition_variable cv_;
std::queue<int> queue_;
};
Multiple mutexes
// CP.21: std::scoped_lock 用于多个互斥锁(避免死锁)
void transfer(Account& from, Account& to, double amount) {
std::scoped_lock lock(from.mutex_, to.mutex_);
from.balance_ -= amount;
to.balance_ += amount;
}
Anti-Patterns
- Use
volatile for synchronization (CP.8 — it is only for hardware I/O)
- Detaching threads (CP.26 — lifetime management becomes nearly impossible)
- Unnamed lock guards:
std::lock_guard<std::mutex>(m); is destroyed immediately (CP.44)
- Holding a lock while calling a callback (CP.22 — risk of deadlock)
- Lock-free programming without deep expertise (CP.100)
Templates and generic programming (T.*)
Core rules
| Rule |
Summary |
| T.1 |
Use templates to raise the level of abstraction |
| T.2 |
Use templates to express algorithms for multiple argument types |
| T.10 |
Specify concepts for all template parameters |
| T.11 |
Use standard concepts whenever possible |
| T.13 |
Prefer the shorthand notation for simple concepts |
| T.43 |
Prefer using over typedef |
| T.120 |
Use template metaprogramming only when truly necessary |
| T.144 |
Do not specialize function templates (use overloading instead) |
Concepts (C++20)
#include <concepts>
// T.10 + T.11: 使用标准概念约束模板
template<std::integral T>
T gcd(T a, T b) {
while (b != 0) {
a = std::exchange(b, a % b);
}
return a;
}
// T.13: 简写概念语法
void sort(std::ranges::random_access_range auto& range) {
std::ranges::sort(range);
}
// 针对领域特定约束的自定义概念
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)
- Using template metaprogramming when
constexpr is sufficient (T.120)
- Use
typedef instead of using (T.43)
Standard library (SL.*)
Core rules
| Rule |
Summary |
| SL.1 |
Use libraries whenever possible |
| SL.2 |
Prefer the standard library over other libraries |
| SL.con.1 |
Prefer std::array or std::vector over C arrays |
| SL.con.2 |
Prefer std::vector |
| 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(using '\n' —— endl forces buffer flushing) |
// SL.con.1 + SL.con.2: 优先选择 vector/array 而非 C 数组
const std::array<int, 4> fixed_data{1, 2, 3, 4};
std::vector<std::string> dynamic_data;
// SL.str.1 + SL.str.2: string 拥有所有权,string_view 负责观察
std::string build_greeting(std::string_view name) {
return "Hello, " + std::string(name) + "!";
}
// SL.io.50: 使用 '\n' 而非 endl
std::cout << "result: " << value << '\n';
Enumerations (Enum.*)
Core rules
| Rule |
Summary |
| Enum.1 |
Prefer enumerations over macros |
| Enum.3 |
Prefer enum class over plain enum |
| Enum.5 |
Do not use ALL_CAPS naming for enumerators |
| Enum.6 |
Avoid anonymous enumerations |
// Enum.3 + Enum.5: 有作用域枚举,非全大写
enum class Color { red, green, blue };
enum class LogLevel { debug, info, warning, error };
// 不良实践:普通枚举会泄露名称,全大写会与宏冲突
enum { RED, GREEN, BLUE }; // 违反 Enum.3 + Enum.5 + Enum.6
#define MAX_SIZE 100 // 违反 Enum.1 —— 应使用 constexpr
Source files and naming (SF., NL.)
Core rules
| Rule |
Summary |
| SF.1 |
Use .cppfor code files and .h |
| SF.7 |
Do not write using namespace |
| SF.8 |
Use .h for all #include guards |
| SF.11 |
Header files should be self-contained |
| NL.5 |
Avoid encoding type information in names (do not use Hungarian notation) |
| NL.8 |
Use a consistent naming style |
| NL.9 |
Use ALL_CAPS only for macro names |
| NL.10 |
Prefer 下划线风格 (underscore_style) naming |
Header Guard
// SF.8: 包含保护 (或 #pragma once)
#ifndef PROJECT_MODULE_WIDGET_H
#define PROJECT_MODULE_WIDGET_H
// SF.11: 自包含 —— 包含此头文件所需的所有内容
#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
// NL.8 + NL.10: 一致的下划线风格
namespace my_project {
constexpr int max_buffer_size = 4096; // NL.9: 非全大写(它不是宏)
class tcp_connection { // 下划线风格的类名
public:
void send_message(std::string_view msg);
bool is_connected() const;
private:
std::string host_; // 成员变量带后缀下划线
int port_;
};
} // namespace my_project
Anti-Patterns
- Use in the global scope of a header file
using namespace std; (SF.7)
- Header files that depend on include order (SF.10, SF.11)
- Hungarian notation, such as
strName、iCount (NL.5)
- ALL_CAPS for anything other than macros (NL.9)
Performance (Per.*)
Core Rules
| Rule |
Summary |
| Per.1 |
Do not optimize without reason |
| Per.2 |
Do not optimize prematurely |
| Per.6 |
Without measurement, do not draw conclusions about performance |
| Per.7 |
Design that aims to support optimization |
| Per.10 |
Rely on the static type system |
| Per.11 |
Move computation from runtime to compile time |
| Per.19 |
Access memory in a predictable way |
Guidelines
// Per.11: 尽可能在编译时计算
constexpr auto lookup_table = [] {
std::array<int, 256> table{};
for (int i = 0; i < 256; ++i) {
table[i] = i * i;
}
return table;
}();
// Per.19: 优先选择连续数据以实现缓存友好
std::vector<Point> points; // 推荐:连续存储
std::vector<std::unique_ptr<Point>> indirect_points; // 不良实践:指针追踪
Anti-Patterns
- Optimizing without performance 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 Self-Check List
Before marking C++ work as complete:
- No raw
new/delete — use smart pointers or RAII (R.11)
- Objects are initialized at declaration (ES.20)
- Variables are set to by default
const/constexpr (Con.1, ES.25)
- Member functions are set to as much as possible
const (Con.2)
- Use
enum class instead of plain enum (Enum.3)
- Use
nullptr instead of 0/NULL (ES.47)
- No narrowing conversions (ES.46)
- No C-style casts (ES.48)
- Single-argument constructors are set to
explicit (C.46)
- The Rule of Zero or Rule of Five is applied (C.20, C.21)
- Base class destructors are public virtual or protected non-virtual (C.35)
- Templates are constrained by Concepts (T.10)
- In the global scope of a header file, there is no
using namespace (SF.7)
- Header files 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)
- Use
'\n' instead of std::endl (SL.io.50)
- No magic numbers (ES.45)