cpp-coding-standards
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseC++ Coding Standards (C++ Core Guidelines)
C++编码标准(C++ Core Guidelines)
Comprehensive coding standards for modern C++ (C++17/20/23) derived from the C++ Core Guidelines. Enforces type safety, resource safety, immutability, and clarity.
这份针对现代C++(C++17/20/23)的全面编码标准,源自C++ Core Guidelines,旨在强化类型安全、资源安全、不可变性与代码清晰度。
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., vs
enum, raw pointer vs smart pointer)enum class
- 编写新的C++代码(类、函数、模板)
- 评审或重构现有C++代码
- 在C++项目中做架构决策
- 在C++代码库中推行一致的编码风格
- 选择语言特性(例如与
enum对比、原生指针与智能指针对比)enum class
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)
- 非C++项目
- 无法适配现代C++特性的遗留C代码库
- 特定规则与硬件约束冲突的嵌入式/裸机环境(可选择性调整)
Cross-Cutting Principles
跨领域原则
These themes recur across the entire guidelines and form the foundation:
- RAII everywhere (P.8, R.1, E.6, CP.20): Bind resource lifetime to object lifetime
- Immutability by default (P.10, Con.1-5, ES.25): Start with /
const; mutability is the exceptionconstexpr - Type safety (P.4, I.4, ES.46-49, Enum.3): Use the type system to prevent errors at compile time
- Express intent (P.3, F.1, NL.1-2, T.10): Names, types, and concepts should communicate purpose
- Minimize complexity (F.2-3, ES.5, Per.4-5): Simple code is correct code
- Value semantics over pointer semantics (C.10, R.3-5, F.20, CP.31): Prefer returning by value and scoped objects
这些核心主题贯穿所有准则,是标准的基础:
- 处处使用RAII(P.8、R.1、E.6、CP.20):将资源生命周期与对象生命周期绑定
- 默认不可变(P.10、Con.1-5、ES.25):优先使用/
const;可变性是例外情况constexpr - 类型安全(P.4、I.4、ES.46-49、Enum.3):利用类型系统在编译期预防错误
- 表达意图(P.3、F.1、NL.1-2、T.10):命名、类型与概念应清晰传达设计目的
- 最小化复杂度(F.2-3、ES.5、Per.4-5):简洁的代码更易保证正确性
- 值语义优先于指针语义(C.10、R.3-5、F.20、CP.31):优先按值返回与作用域内对象
Philosophy & Interfaces (P., I.)
设计理念与接口(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 |
| 规则 | 摘要 |
|---|---|
| P.1 | 直接用代码表达设计思路 |
| P.3 | 清晰表达代码意图 |
| P.4 | 程序理想状态是静态类型安全的 |
| P.5 | 优先编译期检查而非运行期检查 |
| P.8 | 禁止资源泄漏 |
| P.10 | 优先使用不可变数据而非可变数据 |
| I.1 | 接口需显式定义 |
| I.2 | 避免非const全局变量 |
| I.4 | 接口需精确且强类型化 |
| I.11 | 绝不要通过原生指针或引用转移所有权 |
| I.23 | 减少函数参数数量 |
DO
正确示例
cpp
// P.10 + I.4: Immutable, strongly typed interface
struct Temperature {
double kelvin;
};
Temperature boil(const Temperature& water);cpp
// P.10 + I.4: 不可变、强类型接口
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 violationcpp
// 弱接口:所有权模糊、单位不明确
double boil(double* temp);
// 非const全局变量
int g_counter = 0; // 违反I.2规则Functions (F.*)
函数(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 |
| F.6 | If your function must not throw, declare it |
| F.8 | Prefer pure functions |
| F.16 | For "in" parameters, pass cheaply-copied types by value and others by |
| 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 |
| 规则 | 摘要 |
|---|---|
| F.1 | 将有意义的操作封装为命名清晰的函数 |
| F.2 | 一个函数应只执行单一逻辑操作 |
| F.3 | 函数应简短简洁 |
| F.4 | 若函数可在编译期计算,声明为 |
| F.6 | 若函数绝对不能抛出异常,声明为 |
| F.8 | 优先使用纯函数 |
| F.16 | 对于"输入"参数,轻量复制类型按值传递,其他类型按 |
| F.20 | 对于"输出"值,优先返回值而非输出参数 |
| F.21 | 若要返回多个"输出"值,优先返回结构体 |
| F.43 | 绝不要返回局部对象的指针或引用 |
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 thiscpp
// F.16: 轻量类型按值传递,其他按const&
void print(int x); // 轻量:按值
void analyze(const std::string& data); // 重量级:按const&
void transform(std::string s); // 接收端:按值(会触发移动)
// 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
纯函数与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);cpp
// 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 from functions (F.45)
T&& - Using / C-style variadics (F.55)
va_arg - Capturing by reference in lambdas passed to other threads (F.53)
- Returning which inhibits move semantics (F.49)
const T
- 从函数返回(F.45)
T&& - 使用/C风格可变参数(F.55)
va_arg - 在传递给其他线程的lambda中按引用捕获变量(F.53)
- 返回会抑制移动语义(F.49)
const T
Classes & Class Hierarchies (C.*)
类与类层次结构(C.*)
Key Rules
核心规则
| Rule | Summary |
|---|---|
| C.2 | Use |
| 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 |
| 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 |
| C.67 | A polymorphic class should suppress public copy/move |
| C.128 | Virtual functions: specify exactly one of |
| 规则 | 摘要 |
|---|---|
| C.2 | 存在不变式时用 |
| C.9 | 最小化成员的可见性 |
| C.20 | 若能避免定义默认操作,就不要定义(零规则) |
| C.21 | 若定义或 |
| C.35 | 基类析构函数:要么是public virtual,要么是protected non-virtual |
| C.41 | 构造函数应创建完全初始化的对象 |
| C.46 | 单参数构造函数需声明为 |
| C.67 | 多态类应禁用公开的复制/移动操作 |
| C.128 | 虚函数:必须明确指定 |
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
};cpp
// C.20:让编译器自动生成特殊成员函数
struct Employee {
std::string name;
std::string department;
int id;
// 无需定义析构函数、复制/移动构造函数或赋值运算符
};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_;
};cpp
// 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 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_;
};cpp
// 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 /
memseton non-trivial types (C.90)memcpy - Providing different default arguments for virtual function and overrider (C.140)
- Making data members or references, which suppresses move/copy (C.12)
const
- 在构造函数/析构函数中调用虚函数(C.82)
- 在非平凡类型上使用/
memset(C.90)memcpy - 为虚函数与重写函数提供不同的默认参数(C.140)
- 将数据成员设为或引用,这会抑制移动/复制操作(C.12)
const
Resource Management (R.*)
资源管理(R.*)
Key Rules
核心规则
| Rule | Summary |
|---|---|
| R.1 | Manage resources automatically using RAII |
| R.3 | A raw pointer ( |
| R.5 | Prefer scoped objects; don't heap-allocate unnecessarily |
| R.10 | Avoid |
| R.11 | Avoid calling |
| R.20 | Use |
| R.21 | Prefer |
| R.22 | Use |
| 规则 | 摘要 |
|---|---|
| R.1 | 用RAII自动管理资源 |
| R.3 | 原生指针( |
| R.5 | 优先使用作用域内对象;不要不必要地在堆上分配 |
| R.10 | 避免使用 |
| R.11 | 避免显式调用 |
| R.20 | 用 |
| R.21 | 优先使用 |
| R.22 | 用 |
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());cpp
// 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
RAII模式
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_;
};cpp
// R.1:资源获取即初始化
class FileHandle {
public:
explicit FileHandle(const std::string& path)
: handle_(std::fopen(path.c_str(), "r")) {
if (!handle_) throw std::runtime_error("打开失败: " + 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(R.11)delete - /
malloc()in C++ code (R.10)free() - Multiple resource allocations in a single expression (R.13 -- exception safety hazard)
- where
shared_ptrsuffices (R.21)unique_ptr
- 裸/
new(R.11)delete - 在C++代码中使用/
malloc()(R.10)free() - 在单个表达式中多次分配资源(R.13——存在异常安全风险)
- 在足够的场景下使用
unique_ptr(R.21)shared_ptr
Expressions & Statements (ES.*)
表达式与语句(ES.*)
Key Rules
核心规则
| Rule | Summary |
|---|---|
| ES.5 | Keep scopes small |
| ES.20 | Always initialize an object |
| ES.23 | Prefer |
| ES.25 | Declare objects |
| ES.28 | Use lambdas for complex initialization of |
| ES.45 | Avoid magic constants; use symbolic constants |
| ES.46 | Avoid narrowing/lossy arithmetic conversions |
| ES.47 | Use |
| ES.48 | Avoid casts |
| ES.50 | Don't cast away |
| 规则 | 摘要 |
|---|---|
| ES.5 | 缩小作用域范围 |
| ES.20 | 始终初始化对象 |
| ES.23 | 优先使用 |
| ES.25 | 除非需要修改,否则将对象声明为 |
| ES.28 | 用lambda完成 |
| ES.45 | 避免魔法常量;使用符号常量 |
| ES.46 | 避免窄化/有损算术转换 |
| ES.47 | 使用 |
| ES.48 | 避免类型转换 |
| ES.50 | 不要移除 |
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;
}();cpp
// 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:用lambda完成复杂const变量初始化
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 or
0as pointer (ES.47 -- useNULL)nullptr - C-style casts (ES.48 -- use ,
static_cast, etc.)const_cast - Casting away (ES.50)
const - Magic numbers without named constants (ES.45)
- Mixing signed and unsigned arithmetic (ES.100)
- Reusing names in nested scopes (ES.12)
- 未初始化变量(ES.20)
- 用或
0作为指针(ES.47——应使用NULL)nullptr - C风格类型转换(ES.48——应使用、
static_cast等)const_cast - 移除限定(ES.50)
const - 未命名的魔法数字(ES.45)
- 混合有符号与无符号算术运算(ES.100)
- 在嵌套作用域中重用名称(ES.12)
Error Handling (E.*)
错误处理(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 |
| 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 |
| 规则 | 摘要 |
|---|---|
| E.1 | 在设计初期制定错误处理策略 |
| E.2 | 抛出异常以表示函数无法完成指定任务 |
| E.6 | 用RAII防止资源泄漏 |
| E.12 | 当无法或不允许抛出异常时,使用 |
| E.14 | 使用专门设计的自定义类型作为异常 |
| E.15 | 按值抛出异常,按引用捕获异常 |
| E.16 | 析构函数、释放函数与swap绝不能失败 |
| E.17 | 不要在每个函数中都尝试捕获所有异常 |
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
}cpp
// 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("连接被拒绝", 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 like or string literals (E.14)
int - 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 (E.28)
errno
- 抛出或字符串字面量等内置类型(E.14)
int - 按值捕获异常(存在切片风险)(E.15)
- 空catch块静默吞掉错误
- 用异常做流程控制(E.3)
- 基于等全局状态做错误处理(E.28)
errno
Constants & Immutability (Con.*)
常量与不可变性(Con.*)
All Rules
全部规则
| Rule | Summary |
|---|---|
| Con.1 | By default, make objects immutable |
| Con.2 | By default, make member functions |
| Con.3 | By default, pass pointers and references to |
| Con.4 | Use |
| Con.5 | Use |
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;| 规则 | 摘要 |
|---|---|
| Con.1 | 默认将对象设为不可变 |
| Con.2 | 默认将成员函数设为 |
| Con.3 | 默认将指针和引用传递给 |
| Con.4 | 对构造后不再变化的值使用 |
| Con.5 | 对可在编译期计算的值使用 |
cpp
// 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 & Parallelism (CP.*)
并发与并行(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 |
| CP.20 | Use RAII, never plain |
| CP.21 | Use |
| CP.22 | Never call unknown code while holding a lock |
| CP.42 | Don't wait without a condition |
| CP.44 | Remember to name your |
| CP.100 | Don't use lock-free programming unless you absolutely have to |
| 规则 | 摘要 |
|---|---|
| CP.2 | 避免数据竞争 |
| CP.3 | 最小化可写数据的显式共享 |
| CP.4 | 从任务而非线程的角度思考 |
| CP.8 | 不要用 |
| CP.20 | 使用RAII,绝不使用裸 |
| CP.21 | 用 |
| CP.22 | 持有锁时绝不调用未知代码 |
| CP.42 | 不要无条件等待 |
| CP.44 | 务必为 |
| CP.100 | 除非绝对必要,否则不要使用无锁编程 |
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_;
};cpp
// 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
多互斥锁
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;
}cpp
// 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
反模式
- for synchronization (CP.8 -- it's for hardware I/O only)
volatile - Detaching threads (CP.26 -- lifetime management becomes nearly impossible)
- Unnamed lock guards: destroys immediately (CP.44)
std::lock_guard<std::mutex>(m); - Holding locks while calling callbacks (CP.22 -- deadlock risk)
- Lock-free programming without deep expertise (CP.100)
- 用做同步(CP.8——它仅用于硬件I/O)
volatile - 分离线程(CP.26——生命周期管理几乎失控)
- 未命名的锁守卫:会立即销毁(CP.44)
std::lock_guard<std::mutex>(m); - 持有锁时调用回调函数(CP.22——存在死锁风险)
- 无专业知识却使用无锁编程(CP.100)
Templates & Generic Programming (T.*)
模板与泛型编程(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 |
| T.120 | Use template metaprogramming only when you really need to |
| T.144 | Don't specialize function templates (overload instead) |
| 规则 | 摘要 |
|---|---|
| T.1 | 用模板提升抽象层级 |
| T.2 | 用模板为多种参数类型实现算法 |
| T.10 | 为所有模板参数指定concept |
| T.11 | 尽可能使用标准concept |
| T.13 | 简单concept优先使用简写语法 |
| T.43 | 优先使用 |
| T.120 | 仅在真正需要时才使用模板元编程 |
| T.144 | 不要特化函数模板(优先重载) |
Concepts (C++20)
Concept(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);cpp
#include <concepts>
// T.10 + T.11:用标准concept约束模板
template<std::integral T>
T gcd(T a, T b) {
while (b != 0) {
a = std::exchange(b, a % b);
}
return a;
}
// T.13:Concept简写语法
void sort(std::ranges::random_access_range auto& range) {
std::ranges::sort(range);
}
// 针对特定领域约束的自定义concept
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 suffices (T.120)
constexpr - instead of
typedef(T.43)using
- 在可见命名空间中使用无约束模板(T.47)
- 特化函数模板而非重载(T.144)
- 在足够的场景下使用模板元编程(T.120)
constexpr - 用而非
typedef(T.43)using
Standard Library (SL.*)
标准库(SL.*)
Key Rules
核心规则
| Rule | Summary |
|---|---|
| SL.1 | Use libraries wherever possible |
| SL.2 | Prefer the standard library to other libraries |
| SL.con.1 | Prefer |
| SL.con.2 | Prefer |
| SL.str.1 | Use |
| SL.str.2 | Use |
| SL.io.50 | Avoid |
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';| 规则 | 摘要 |
|---|---|
| SL.1 | 代码文件用 |
| SL.2 | 优先使用标准库而非其他库 |
| SL.con.1 | 优先使用 |
| SL.con.2 | 默认优先使用 |
| SL.str.1 | 用 |
| SL.str.2 | 用 |
| SL.io.50 | 避免使用 |
cpp
// 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 << "结果: " << value << '\n';Enumerations (Enum.*)
枚举(Enum.*)
Key Rules
核心规则
| Rule | Summary |
|---|---|
| Enum.1 | Prefer enumerations over macros |
| Enum.3 | Prefer |
| 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| 规则 | 摘要 |
|---|---|
| Enum.1 | 优先使用枚举而非宏 |
| Enum.3 | 优先使用 |
| Enum.5 | 枚举器不要用全大写 |
| Enum.6 | 避免未命名枚举 |
cpp
// 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规则——应使用constexprSource Files & Naming (SF., NL.)
源文件与命名(SF.、NL.)
Key Rules
核心规则
| Rule | Summary |
|---|---|
| SF.1 | Use |
| SF.7 | Don't write |
| SF.8 | Use |
| 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 |
| 规则 | 摘要 |
|---|---|
| SF.1 | 代码文件用 |
| SF.7 | 不要在头文件的全局作用域中写 |
| SF.8 | 所有 |
| SF.11 | 头文件应自包含 |
| NL.5 | 避免在名称中编码类型信息(不要用匈牙利命名法) |
| NL.8 | 使用一致的命名风格 |
| NL.9 | 仅宏名称使用全大写 |
| NL.10 | 优先使用下划线分隔命名法 |
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_Hcpp
// 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_HNaming 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_projectcpp
// 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_projectAnti-Patterns
反模式
- in a header at global scope (SF.7)
using namespace std; - Headers that depend on inclusion order (SF.10, SF.11)
- Hungarian notation like ,
strName(NL.5)iCount - ALL_CAPS for anything other than macros (NL.9)
- 在头文件全局作用域中写(SF.7)
using namespace std; - 依赖包含顺序的头文件(SF.10、SF.11)
- 匈牙利命名法如、
strName(NL.5)iCount - 非宏名称使用全大写(NL.9)
Performance (Per.*)
性能(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 |
| 规则 | 摘要 |
|---|---|
| Per.1 | 无理由不要优化 |
| Per.2 | 不要过早优化 |
| Per.6 | 无性能测试数据不要做性能相关声明 |
| Per.7 | 设计时要考虑可优化性 |
| Per.10 | 依赖静态类型系统 |
| Per.11 | 将计算从运行期转移到编译期 |
| Per.19 | 可预测地访问内存 |
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 chasingcpp
// 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 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)
- 无性能分析数据就做优化(Per.1、Per.6)
- 选择“巧妙”的底层代码而非清晰的抽象(Per.4、Per.5)
- 忽略数据布局与缓存行为(Per.19)
Quick Reference Checklist
快速参考检查清单
Before marking C++ work complete:
- No raw /
new-- use smart pointers or RAII (R.11)delete - Objects initialized at declaration (ES.20)
- Variables are /
constby default (Con.1, ES.25)constexpr - Member functions are where possible (Con.2)
const - instead of plain
enum class(Enum.3)enum - instead of
nullptr/0(ES.47)NULL - No narrowing conversions (ES.46)
- No C-style casts (ES.48)
- Single-argument constructors are (C.46)
explicit - 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 in headers at global scope (SF.7)
using namespace - Headers have include guards and are self-contained (SF.8, SF.11)
- Locks use RAII (/
scoped_lock) (CP.20)lock_guard - Exceptions are custom types, thrown by value, caught by reference (E.14, E.15)
- instead of
'\n'(SL.io.50)std::endl - No magic numbers (ES.45)
在完成C++工作前,请检查:
- 无裸/
new——使用智能指针或RAII(R.11)delete - 对象在声明时初始化(ES.20)
- 变量默认设为/
const(Con.1、ES.25)constexpr - 成员函数尽可能设为(Con.2)
const - 用替代普通
enum class(Enum.3)enum - 用替代
nullptr/0(ES.47)NULL - 无窄化转换(ES.46)
- 无C风格类型转换(ES.48)
- 单参数构造函数是(C.46)
explicit - 应用了零规则或五规则(C.20、C.21)
- 基类析构函数是public virtual或protected non-virtual(C.35)
- 模板用concept约束(T.10)
- 头文件全局作用域中无(SF.7)
using namespace - 头文件有包含保护且自包含(SF.8、SF.11)
- 锁使用RAII(/
scoped_lock)(CP.20)lock_guard - 异常是自定义类型,按值抛出、按引用捕获(E.14、E.15)
- 用替代
'\n'(SL.io.50)std::endl - 无魔法数字(ES.45)