C++编程代码规范指南
# C++编程代码规范指南
本规范参考 Google C++ Style Guide (opens new window),结合项目实践精简整理。
# 目录
- 01.规范概述
- 02.头文件规范
- 03.命名规范
- 04.代码格式规范
- 05.注释规范
- 06.类设计规范
- 07.函数规范
- 08.现代C++特性
- 09.异常与错误处理
- 10.并发与性能
- 11.工具链与自动化
- 12.代码审查清单
- 13.常见陷阱速查
# 01.规范概述
# 1.1 为何需要代码规范
疑惑:代码能跑就行,为什么还要花时间制定规范?
答疑:C++ 是一门强大的语言,但也是公认的"最难写对的语言"。同一个功能有 10 种写法,其中 7 种有隐蔽的未定义行为。规范的本质是用统一的方式写安全的代码。
# 1.2 核心目标
| 目标 | 说明 |
|---|---|
| 可读性 | 代码像散文一样流畅 |
| 一致性 | 整个项目像一个人写的 |
| 安全性 | 从编码层面避免内存错误、未定义行为 |
| 可维护性 | 半年后自己还能看懂 |
| 可审查性 | Code Review 聚焦逻辑而非格式 |
# 1.3 要求等级
- 必须(Mandatory):必须采用
- 推荐(Preferable):理应采用,特殊情况可不采用
- 可选(Optional):自行决定
# 1.4 C++版本
代码应针对 C++17,不使用 C++20 特性。不要使用非标准扩展。
# 02.头文件规范
# 2.1 Self-contained 头文件 【必须】
头文件应该自给自足,以 .h 结尾。禁止使用 -inl.h 头文件。
// ✅ 正确:头文件包含了所需的所有依赖
#pragma once
#include <string>
#include <vector>
class UserManager {
public:
std::string getName(int id);
std::vector<int> getIds();
};
// ❌ 错误:依赖使用方先包含了 <string>
#pragma once
class UserManager {
public:
std::string getName(int id); // 编译可能失败!
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 2.2 头文件保护 【必须】
所有头文件使用 #pragma once 或 #define 防止多重包含。
// ✅ 方式一:推荐
#pragma once
// ✅ 方式二:传统宏保护
#ifndef MYPROJECT_FOO_BAR_H_
#define MYPROJECT_FOO_BAR_H_
// ...
#endif // MYPROJECT_FOO_BAR_H_
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 2.3 避免前置声明 【推荐】
优先使用 #include,尽量避免前置声明。
// ✅ 直接包含
#include "user.h"
// ❌ 尽量避免:前置声明隐藏依赖,可能被后续改动破坏
class User;
1
2
3
4
5
2
3
4
5
# 2.4 内联函数 【推荐】
只有 10 行以内的函数才声明为内联。
// ✅ 短小的存取函数适合内联
inline int getId() const { return id_; }
// ❌ 长函数不要内联
inline void processLargeDataset() {
for (int i = 0; i < 10000; ++i) {
// 大量逻辑...
}
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 2.5 #include 顺序 【必须】
// 1. 对应的头文件(优先位置)
#include "foo/public/fooserver.h"
// 2. C 系统头文件
#include <sys/types.h>
#include <unistd.h>
// 3. C++ 标准库
#include <string>
#include <vector>
// 4. 其他库的头文件
#include "base/basictypes.h"
// 5. 本项目头文件
#include "foo/bar.h"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
每组之间用空行分隔。
# 03.命名规范
# 3.1 命名总表
| 类型 | 规则 | 示例 |
|---|---|---|
| 文件名 | 全小写 + 下划线 | user_manager.h, thread_pool.cc |
| 类名 | 大驼峰 | UserManager, ThreadPool |
| 函数名 | 大驼峰 | GetUserName(), ProcessData() |
| 变量名 | 全小写 + 下划线 | user_name, thread_count |
| 成员变量 | 小写 + 下划线 + 后缀 _ | user_name_, count_ |
| 常量 | k 开头 + 大驼峰 | kMaxRetryCount, kDefaultPort |
| 宏名 | 全大写 + 下划线 | MAX_BUFFER_SIZE |
| 命名空间 | 全小写 + 下划线 | my_project::foo_bar |
| 枚举 | 同常量或全大写 | kOk, kError 或 COLOR_RED |
# 3.2 正确与错误示例
// ✅ 正确
class UserManager {
public:
std::string GetUserName(int user_id) const;
private:
std::string user_name_; // 成员变量带下划线后缀
int login_count_;
static constexpr int kMaxRetryCount = 3;
};
// ❌ 错误
class usermanager { // 类名未大驼峰
public:
std::string get_user_name(int user_id); // 函数名未大驼峰
std::string userName; // 成员变量未用下划线后缀
static const int MAX_RETRY = 3; // 常量未用 k 前缀
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 3.3 命名反模式
| 反模式 | 示例 | 改进 |
|---|---|---|
| 过度缩写 | usr_mgr | user_manager |
| 含义不清 | data, info, tmp | user_data, config_info |
| 否定命名 | is_not_empty | is_empty() 反转 |
| 拼音命名 | dian_hua | phone_number |
# 04.代码格式规范
# 4.1 缩进与空格 【必须】
// ✅ 使用 2 个空格缩进(Google 风格)
int main() {
for (int i = 0; i < 10; ++i) {
if (i % 2 == 0) {
std::cout << i << std::endl;
}
}
return 0;
}
// ✅ 运算符两侧加空格
int result = a + b * c;
bool ok = (x > 0) && (y < 10);
// ✅ 逗号后加空格
void Init(const std::string& name, int age, bool active);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 4.2 大括号风格 【必须】
// ✅ K&R 风格:左大括号不换行
class UserService {
public:
void Process() {
if (condition) {
DoSomething();
} else {
DoOther();
}
}
};
// ❌ 左大括号换行(不推荐)
class UserService
{
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 4.3 换行与对齐
// ✅ 参数过长时换行对齐
void SendNotification(
const std::string& user_id,
const std::string& title,
const std::string& content,
NotificationType type);
// ✅ 条件表达式过长时换行
if (user_status == Status::ACTIVE
&& user_role == Role::ADMIN
&& HasPermission(Permission::WRITE)) {
// ...
}
// 单行长度不超过 120 字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 4.4 空行与分隔
class UserService {
public:
// 构造与成员变量之间空一行
UserService(UserRepo* repo) : repo_(repo) {}
// 方法之间空一行
User GetUser(int id) {
User cached = cache_.Get(id);
if (cached.valid()) {
return cached; // 卫语句提前返回
}
User user = repo_->FindById(id); // 不同逻辑块之间空一行
cache_.Put(id, user);
return user;
}
void DeleteUser(int id);
private:
UserRepo* repo_;
Cache cache_;
static constexpr int kMaxRetry = 3; // 不同逻辑组之间空一行
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 05.注释规范
# 5.1 核心原则
注释解释"为什么",代码说明"做了什么"。
// ❌ 差的注释:复述代码
i++; // i 加 1
// ✅ 好的注释:解释意图
i++; // 跳过 CSV 文件的标题行
// ❌ 过时注释(危险!)
// 调用 V1 接口(注:已改为 V2)
ProcessV2();
// ✅ 记录决策原因
// 使用重试机制,因为第三方接口在高峰期偶尔超时(2020-03 确认的问题)
RetryWithBackoff(kMaxRetry);
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 5.2 类注释
// 用户管理服务,负责用户的 CRUD 和权限管理。
//
// 线程安全性:该类是线程安全的,内部用 std::mutex 保护共享数据。
// 使用示例:
// UserService svc(repo, cache);
// auto user = svc.GetUser(123);
class UserService {
public:
// ...
};
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 5.3 函数注释
// 根据 ID 查询用户信息。
//
// 优先从缓存获取,未命中时查数据库。查询结果自动写入缓存。
//
// 参数:
// user_id - 用户唯一标识,必须 > 0
// 返回:
// 用户对象;如果用户不存在,返回 std::nullopt
std::optional<User> GetUser(int user_id);
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 5.4 TODO 与 FIXME
// TODO(yc): 添加分页查询支持,预计 2.0 版本实现
std::vector<User> GetAllUsers();
// FIXME(yc): 当 user_id 为 INT_MAX 时溢出,需要增加边界检查
int64_t CalculateHash(int user_id);
1
2
3
4
5
2
3
4
5
# 06.类设计规范
# 6.1 构造与析构 【必须】
// ✅ 不要在构造函数中调用虚函数
class Base {
public:
Base() { Init(); } // ❌ Init() 调用虚函数——派生的实现不会被调用
virtual void Init() = 0;
};
// ✅ 用工厂函数替代复杂的构造
class UserManager {
public:
static std::unique_ptr<UserManager> Create(const Config& config);
private:
UserManager() = default; // 构造私有,通过工厂创建
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 6.2 隐式类型转换 【必须】
// ✅ 单参数构造函数加 explicit
class StringWrapper {
public:
explicit StringWrapper(const std::string& s); // ✅
// StringWrapper(const std::string& s); // ❌ 可能发生隐式转换
};
1
2
3
4
5
6
2
3
4
5
6
# 6.3 拷贝和移动 【必须】
// ✅ 需要时支持;否则显式禁用
class NonCopyable {
public:
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
};
// ✅ 移动构造函数声明为 noexcept
class Buffer {
public:
Buffer(Buffer&& other) noexcept; // noexcept 使 std::vector 可移动
};
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 6.4 继承 【必须】
// ✅ 优先使用组合,只在"IS-A"关系时使用继承
// 不推荐:继承导致强耦合
class OrderService : public BaseService { };
// 推荐:组合更灵活
class OrderService {
public:
explicit OrderService(BaseService* base) : base_(base) {}
private:
BaseService* base_;
};
// ✅ 继承时:public 继承 + override 关键字
class Dog : public Animal {
public:
void Speak() override; // ✅ 明确标记覆盖
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 6.5 成员变量 【必须】
// ✅ 数据成员全部为 private(static const 除外)
class UserInfo {
public:
int id() const { return id_; } // accessor
void set_name(const std::string& s) { name_ = s; }
private:
int id_;
std::string name_;
static constexpr int kMaxAge = 150;
};
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 6.6 声明顺序 【必须】
class MyClass {
public: // ① public 段在最前
// 类型别名
using IdType = int64_t;
// 常量
static constexpr int kDefaultPort = 8080;
// 构造/析构
MyClass();
~MyClass();
// 非拷贝/移动(按需)
// 方法
void DoSomething();
protected: // ② protected
void HelperMethod();
private: // ③ private 在最后
int value_;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 6.7 运算符重载 【必须】
// ✅ 只在语义明确时重载
class Complex {
public:
Complex operator+(const Complex& other) const; // ✅ 数学语义
bool operator==(const Complex& other) const; // ✅ 比较语义
// ❌ 不要重载 && || , 一元 &
};
// ✅ 定义了 < 就应该定义所有比较运算符
bool operator<(const Foo& a, const Foo& b);
bool operator>(const Foo& a, const Foo& b);
bool operator<=(const Foo& a, const Foo& b);
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 07.函数规范
# 7.1 输入与输出 【推荐】
// ✅ 优先用返回值作为输出
std::optional<std::string> TryParse(const std::string& input);
// 调用方
auto result = TryParse("42");
if (result && *result > 0) {
// 使用 result
}
// ✅ C++17 结构化绑定返回多值
std::tuple<int, std::string> Process(const Request& req);
auto [status, message] = Process(req);
// 参数顺序:输入参数在前,输出参数在后
void FillResponse(const Request& req, Response* resp);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 7.2 编写短函数 【推荐】
- 一个函数超过 40 行 → 考虑拆分
- 一个函数做一件事
- 善用卫语句减少嵌套
// ❌ 嵌套太深
void Process(Order* order) {
if (order != nullptr) {
if (order->IsValid()) {
if (!order->IsProcessed()) {
DoProcess(order);
}
}
}
}
// ✅ 卫语句扁平化
void Process(Order* order) {
if (order == nullptr) return;
if (!order->IsValid()) return;
if (order->IsProcessed()) return;
DoProcess(order);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 7.3 函数重载 【必须】
// ✅ 重载版本之间语义无差异时使用
void Analyze(const std::string& text);
void Analyze(const char* text, size_t length);
// ❌ 仅通过 const 或参数类型细微差别区分 → 让调用者困惑
1
2
3
4
5
2
3
4
5
# 7.4 缺省参数 【推荐】
// ✅ 非虚函数中可用
void Connect(const std::string& host, int port = 8080);
// ❌ 虚函数中禁止使用(派生类的默认值可能不同)
1
2
3
4
2
3
4
# 7.5 Lambda 表达式 【推荐】
// ✅ 短 lambda:默认按引用捕获,写在一行
std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; });
// ✅ 需要捕获外部变量时,优先显式列出
int threshold = 10;
auto it = std::find_if(v.begin(), v.end(),
[threshold](int x) { return x > threshold; });
// ✅ 泛型 lambda(C++14)
auto twice = [](auto x) { return x * 2; };
// ✅ 捕获 this 时,C++17 起用 *this 避免悬空
class Worker {
void Schedule() {
timer_.Async([*this] { Process(); }); // 拷贝整个对象
// timer_.Async([this] { Process(); }); // ❌ this 可能已析构
}
};
// ✅ 长 lambda(>5 行)考虑提取为具名函数
// ❌ 避免:lambda 内又嵌套 lambda(可读性灾难)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 08.现代C++特性
# 8.1 智能指针 【必须】
// ✅ std::unique_ptr:独占所有权
std::unique_ptr<User> CreateUser(const std::string& name);
// ✅ std::shared_ptr:共享所有权(仅在确实需要时使用)
std::shared_ptr<Config> GetGlobalConfig();
// ❌ 不要到处使用 shared_ptr(有引用计数开销)
// ❌ 不要手动 new/delete(用智能指针或容器管理)
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 8.2 右值引用与移动语义 【推荐】
// ✅ 定义移动构造函数(noexcept)
class Buffer {
public:
Buffer(Buffer&& other) noexcept : data_(other.data_), size_(other.size_) {
other.data_ = nullptr;
other.size_ = 0;
}
private:
char* data_;
size_t size_;
};
// ✅ std::move 转移所有权
auto buffer2 = std::move(buffer1);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 8.3 const 与 constexpr 【必须】
// ✅ API 中合理使用 const
std::string GetName() const; // const 成员函数
void Process(const std::string& input); // const 引用参数
// ✅ constexpr 定义编译时常量
constexpr int kMaxConnections = 100;
constexpr int Square(int x) { return x * x; } // constexpr 函数
1
2
3
4
5
6
7
2
3
4
5
6
7
# 8.4 类型转换 【必须】
// ✅ 使用 C++ 风格的类型转换
int64_t y = int64_t{1} << 42; // 括号初始化
double d = static_cast<double>(i); // static_cast
const_cast<char*>(str); // const_cast(谨慎)
reinterpret_cast<uintptr_t>(ptr); // reinterpret_cast(极谨慎)
// ❌ 不要使用 C 风格转换
int y = (int)x; // 禁止
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 8.5 auto 关键字 【推荐】
// ✅ 迭代器、lambda 等类型冗长时使用
auto it = container.begin();
auto result = std::make_unique<ComplexType>(args...);
// ❌ 不要滥用:当显式类型让代码更清晰时,写出类型
int user_count = 0; // ✅ 比 auto 更清晰
auto user_count = 0; // ❌ 不清楚是 int 还是 size_t
1
2
3
4
5
6
7
2
3
4
5
6
7
# 8.6 前置自增 【必须】
// ✅ 对迭代器和模板类型使用 ++i
for (auto it = v.begin(); it != v.end(); ++it) { }
// ✅ 循环中对简单数值,两者都行
for (int i = 0; i < 10; ++i) { }
1
2
3
4
5
2
3
4
5
# 8.7 整型 【推荐】
// ✅ 普通整型使用 int
int count = 0;
for (int i = 0; i < 10; ++i) { }
// ✅ 需要确定大小时用 <cstdint> 的精确宽度类型
int64_t large_value = 0;
uint32_t flags = 0;
// ❌ 避免使用无符号整型(除非位运算或表示位组)
// 不要仅为了"不会为负"而用 unsigned
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 8.8 结构化绑定 【推荐】
// ✅ 返回多值:用结构化绑定接收
std::tuple<int, std::string> GetUserInfo();
auto [id, name] = GetUserInfo(); // C++17
// ✅ 遍历 map
std::map<int, std::string> m = {{1, "a"}, {2, "b"}};
for (const auto& [key, value] : m) {
std::cout << key << " -> " << value << '\n';
}
// ✅ 解构 struct / pair
struct Point { int x, y; };
Point p{10, 20};
auto [px, py] = p; // px=10, py=20
// ❌ 滥用:当字段名本身已足够清晰时,不需要结构化绑定
int user_id = user.id(); // ✅ 更清晰
auto [id, name] = user.GetTuple(); // ❌ id 是什么 id?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 8.9 std::optional 与 std::variant 【推荐】
// ✅ std::optional:表达"可能没有值"
std::optional<User> FindUser(int id) {
if (auto it = users_.find(id); it != users_.end()) {
return it->second;
}
return std::nullopt;
}
// 调用方:
auto user = FindUser(123);
if (user) { Process(*user); } // ✅ 显式检查
// *user // ❌ 未检查直接解引用——UB
// user.value() // ❌ 抛 std::bad_optional_access
auto name = user.value_or(User{"unknown"}); // ✅ 安全默认值
// ✅ std::variant:类型安全的联合体
using Result = std::variant<User, Error, Timeout>;
Result r = FetchRemote(123);
std::visit([](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, User>) { Process(arg); }
else if constexpr (std::is_same_v<T, Error>) { LogError(arg); }
}, r);
// ❌ 过度的 variant 让调用方负担过重——超过 4 个备选考虑重构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 8.10 if 与 switch 初始化语句 【推荐】
// ✅ if-init(C++17):把变量作用域限制在 if 块内
if (auto it = map.find(key); it != map.end()) {
Process(it->second); // it 仅这里可见
} // it 析构
// ✅ 等价于老写法,但更干净:
auto it = map.find(key);
if (it != map.end()) { Process(it->second); }
// it 仍然可见,作用域泄露
// ✅ switch-init(C++17)
switch (auto status = GetStatus(); status) {
case Status::OK: return;
case Status::RETRY: return Retry();
}
// ✅ lock + if-init:完美搭档
if (std::lock_guard g(mu_); cache_.contains(key)) {
return cache_[key];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 8.11 枚举与枚举类 【必须】
// ✅ C++11 起一律用 enum class(强类型枚举)
enum class Color { Red, Green, Blue };
enum class Status { OK, Error, Timeout };
Color c = Color::Red; // ✅ 必须带作用域
// int x = Color::Red; // ❌ 不隐式转 int
int x = static_cast<int>(Color::Red); // ✅ 显式转换
// ✅ 指定底层类型(二进制协议 / 节省空间)
enum class Perm : uint8_t { Read = 0x1, Write = 0x2, Exec = 0x4 };
static_assert(sizeof(Perm) == 1);
// ❌ 禁止 C 风格 enum
enum OldColor { RED, GREEN }; // ❌ 污染作用域,隐式转 int
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 09.异常与错误处理
# 9.1 异常使用策略
// ✅ 保持团队一致:要么全用异常,要么全不用
// 对于不用异常的项目:
// - 用返回码或 std::optional 报告错误
// - 不要引入抛出异常的代码
// ✅ 使用异常时:
// - 按值抛出,按引用捕获
// - 不要到处捕获异常,只捕获能处理的
// - 确保异常安全:使用 RAII 管理资源
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 9.2 RAII 原则 【必须】
// ✅ RAII:资源获取即初始化
class FileGuard {
public:
explicit FileGuard(const char* path) : fp_(fopen(path, "r")) {}
~FileGuard() { if (fp_) fclose(fp_); }
FILE* get() const { return fp_; }
private:
FILE* fp_;
};
// ❌ 手动管理资源
void BadExample() {
FILE* fp = fopen("data.txt", "r");
// ... 如果中间 return 或抛异常,fp 泄漏
fclose(fp);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 9.3 错误码与返回值 【推荐】
// ✅ 用 std::optional 表示可能找不到
std::optional<User> FindUser(int id);
// ✅ 用 std::expected(C++23)或自定义 Result 表示可能失败
// C++23:
std::expected<User, Error> CreateUser(std::string_view name);
// C++17 替代:
struct Result { User user; Error error; bool ok; };
// C++17 之前:
std::pair<User, Error> CreateUser(std::string_view name); // 不太好
// ✅ 布尔返回值带 [[nodiscard]](C++17)
[[nodiscard]] bool TryLock();
TryLock(); // ⚠ 编译器警告:忽略了返回值
// ❌ 用输出参数报错(不透明)
bool CreateUser(const std::string& name, User* out, Error* err);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
错误处理分层:
┌─────────────────────────────────────────────┐
│ 层 │ 策略 │
├────────────┼────────────────────────────────┤
│ 对外 API │ 返回 optional / Result │
│ 内部函数 │ assert + 返回码 │
│ 不可能路径 │ assert / abort │
│ 构造函数 │ 抛异常(因为不能返回值) │
└─────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 9.4 noexcept 规范 【推荐】
// ✅ 移动构造/赋值加 noexcept(STL 容器依赖它)
class Buffer {
public:
Buffer(Buffer&& other) noexcept; // ✅
Buffer& operator=(Buffer&& other) noexcept; // ✅
};
// ✅ 不抛异常的函数加 noexcept
int GetValue() const noexcept; // 纯返回成员
void Swap(Foo& a, Foo& b) noexcept; // 交换操作
// ✅ 析构函数默认 noexcept,不要主动抛异常
~Resource() {
// ❌ 析构里抛异常 → std::terminate
// ✅ 如果必须处理,吞掉并 log
}
// ❌ 不保证 noexcept 的函数不要标记
// 因为 noexcept 等于一个运行时承诺:抛了就 abort
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 9.5 断言与防御 【推荐】
// ✅ 前置条件用 assert 检查
User* GetUser(int id) {
assert(id > 0 && "user_id must be positive");
return FindInCache(id);
}
// ✅ 防御性编程:对外接口检查参数
void ProcessRequest(const Request* req) {
if (req == nullptr) {
LOG(ERROR) << "null request";
return;
}
// ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 10.并发与性能
# 10.1 线程安全 【必须】
// ✅ 明确标注线程安全性
// 线程安全的类
class ThreadSafeCache {
public:
void Put(const std::string& key, const std::string& value);
private:
mutable std::mutex mu_;
std::unordered_map<std::string, std::string> data_ GUARDED_BY(mu_);
};
// ✅ 优先使用 std::mutex + std::lock_guard
void UpdateData() {
std::lock_guard<std::mutex> lock(mu_);
data_["key"] = "value";
}
// ✅ 加锁顺序一致,避免死锁
// 始终按相同顺序获取多个锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 10.2 锁选型指南 【推荐】
// 决策树:
//
// 单变量? ───yes──▶ std::atomic<T>
// 多变量? ───yes──▶
// 读写比 > 10:1 且临界区 > 1μs?
// ───yes──▶ std::shared_mutex
// ───no ──▶ std::mutex + lock_guard
//
// ✅ 90% 场景:std::mutex + std::lock_guard
class Counter {
public:
void Inc() { std::lock_guard g(mu_); ++n_; }
int Snapshot() const { std::lock_guard g(mu_); return n_; }
private:
mutable std::mutex mu_;
int n_ = 0;
};
// ✅ 同时锁多把:std::scoped_lock(C++17,防死锁)
void Transfer(Account& a, Account& b, long n) {
std::scoped_lock g(a.mu_, b.mu_); // 原子锁两把
a.balance_ -= n; b.balance_ += n;
}
// ✅ 读写锁:临界区大 且 读 >> 写
std::shared_mutex rw_;
std::string Read() const {
std::shared_lock g(rw_); // 共享读
return data_;
}
void Write(std::string s) {
std::unique_lock g(rw_); // 排他写
data_ = std::move(s);
}
// ❌ 反模式:手动 lock/unlock
mu_.lock();
// ... 可能抛异常
mu_.unlock(); // 可能永远走不到
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 10.3 原子操作 【推荐】
// ✅ 计数器用 std::atomic(无锁)
std::atomic<size_t> req_count_{0};
void OnRequest() { req_count_.fetch_add(1, std::memory_order_relaxed); }
// ✅ 状态标志用 atomic<bool>
std::atomic<bool> stop_{false};
void Worker() {
while (!stop_.load(std::memory_order_acquire)) {
DoWork();
}
}
// ✅ 发布大对象:release/acquire 配对
std::string data_; // 数据
std::atomic<bool> ready_{false}; // 标志
// 发布端
void Publish(std::string s) {
data_ = std::move(s); // ① 先写数据
ready_.store(true, std::memory_order_release); // ② 再设标志
}
// 消费端
std::string Consume() {
if (ready_.load(std::memory_order_acquire)) { // ③ 先读标志
return data_; // ④ 读到完整数据
}
return "";
}
// ❌ 多个相关变量用多个 atomic——仍然有竞争,回到锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 10.4 线程局部存储 【必须】
// ✅ 函数内 thread_local 可自由使用
const Foo& GetThreadLocalFoo() {
thread_local Foo foo = CreateFoo();
return foo;
}
1
2
3
4
5
2
3
4
5
# 10.5 性能优化
// ✅ 容器预分配
std::vector<int> v;
v.reserve(1000); // 已知大小时预分配
// ✅ std::string_view 替代 const std::string&(C++17)
void Process(std::string_view input); // 零拷贝,兼容 const char* 和 std::string
// ✅ 循环中用 ++it 而非 it++
for (auto it = v.begin(); it != v.end(); ++it) { }
// ✅ 按 const 引用传参,避免大对象拷贝
void ProcessUser(const User& user); // 传引用
void SetName(std::string name); // 需要拷贝时传值 + move
// ✅ 返回值优化(RVO / NRVO):放心 return 局部对象
std::vector<int> BuildVector() {
std::vector<int> v(1000);
return v; // ✅ 编译器自动移动或原地构造
}
// ❌ 不要写 return std::move(v); —— 反而阻止 RVO
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 10.6 缓存友好的数据结构 【推荐】
// ✅ 紧凑内存布局:数组优于链表(cache 友好)
// ❌ 分散在堆上的链表每一跳都可能 cache miss
struct Point { float x, y, z; };
std::vector<Point> points(10000); // 连续内存,预取友好
// ✅ 结构体字段按访问频率排序(热数据放一起)
struct HotCold {
int freq_accessed_; // 热点数据:前面
long padding1_[64];
char rarely_used_[256]; // 冷数据:后面
};
// ✅ 避免伪共享(false sharing)
struct alignas(64) PaddedCounter { // 独占 cache line
std::atomic<long> cnt{0};
};
// 或 C++17:
// alignas(std::hardware_destructive_interference_size)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 11.工具链与自动化
# 11.1 静态代码检查
| 工具 | 用途 | 集成 |
|---|---|---|
| clang-format | 代码格式化 | 保存时自动 |
| clang-tidy | 静态分析、风格检查 | CI / IDE |
| cpplint | Google 风格检查 | CI |
| Cppcheck | Bug 检测 | CI |
| AddressSanitizer | 内存错误检测 | 单元测试 |
# 11.2 clang-format 配置
# .clang-format
BasedOnStyle: Google
ColumnLimit: 120
IndentWidth: 2
AccessModifierOffset: -1
1
2
3
4
5
2
3
4
5
# 11.3 CI 集成
# GitHub Actions 示例
- name: Code Quality
run: |
clang-format --dry-run --Werror src/**/*.cc # 格式检查
clang-tidy src/**/*.cc -- -std=c++17 # 静态分析
cmake --build . --target test # 单元测试
1
2
3
4
5
6
2
3
4
5
6
# 11.4 编译期防御 【推荐】
// ✅ static_assert:编译期保证
static_assert(sizeof(int) >= 4, "int must be at least 32 bits");
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
// ✅ [[nodiscard]]:返回值不可丢弃(C++17)
[[nodiscard]] bool TryLock();
TryLock(); // ⚠ 编译警告:忽略了返回值
// ✅ = delete:禁止拷贝
class NonCopyable {
public:
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
};
// ✅ explicit:防止隐式转换
explicit Timer(std::chrono::milliseconds ms);
// ✅ 编译选项(CMake)
target_compile_options(myapp PRIVATE
-Wall -Wextra -Wpedantic -Wshadow
-Wnon-virtual-dtor -Wold-style-cast
-Werror)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 11.5 pre-commit 钩子 【推荐】
# .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: v17.0.6
hooks:
- id: clang-format
types_or: [c++, c]
1
2
3
4
5
6
7
2
3
4
5
6
7
# 安装
pip install pre-commit
pre-commit install # 每次 commit 前自动执行
pre-commit run --all-files # 手动全量检查
1
2
3
4
2
3
4
# 12.代码审查清单
每次 Code Review 时,按以下清单逐项检查:
## 命名
- [ ] 类名大驼峰、变量全小写下划线、常量 k 开头
- [ ] 不存在拼音、单字母、含义模糊的命名
- [ ] 成员变量有下划线后缀 (_)
## 头文件
- [ ] 有头文件保护 (#pragma once 或 #define)
- [ ] #include 顺序正确
- [ ] 无多余的前置声明
## 格式
- [ ] 缩进统一(2 空格)
- [ ] 大括号 K&R 风格
- [ ] 单行不超过 120 字符
## 类设计
- [ ] 构造函数中无虚函数调用
- [ ] 单参数构造函数有 explicit
- [ ] 数据成员 private
- [ ] 优先组合而非继承
## 内存与安全
- [ ] 无裸 new/delete(用智能指针)
- [ ] 资源管理用 RAII
- [ ] 无越界访问、无悬空指针嫌疑
- [ ] 加锁顺序一致
- [ ] 移动构造/赋值标记 noexcept
## 现代 C++
- [ ] 用 nullptr 而非 NULL
- [ ] 用 auto 简化冗长类型
- [ ] 用 override 标记虚函数覆盖
- [ ] 用 std::optional 表示可选值
- [ ] 用 std::string_view 传只读字符串
- [ ] 用 enum class 替代 C 风格 enum
- [ ] 用 [[nodiscard]] 标记不可忽略的返回值
## 性能
- [ ] 循环外声明的对象在循环内是否合理
- [ ] 容器预分配了合理容量
- [ ] 大对象按引用传递
- [ ] 无 return std::move(...) 阻止 RVO
- [ ] 结构体布局缓存友好(热字段在前)
## 测试
- [ ] 核心逻辑有单元测试
- [ ] 正常和异常路径都覆盖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 13.常见陷阱速查
以下陷阱即使在有经验的开发者中也常见,值得定期回顾。
# 13.1 生命周期陷阱
| # | 陷阱 | 描述 | 正解 |
|---|---|---|---|
| 1 | 返回局部引用 | const std::string& f() { string s = ...; return s; } | 按值返回 |
| 2 | auto 剥引用 | auto x = GetRef(); 发生拷贝 | auto& x = GetRef(); |
| 3 | range-for 拷贝 | for (auto x : v) 拷贝每个元素 | for (const auto& x : v) |
| 4 | lambda 捕获悬空 | [this] { ... } 异步执行时 this 已析构 | [*this](C++17) |
| 5 | const char* 临时 | auto p = (std::string("a") + "b").c_str(); | 先接住 string 对象 |
# 13.2 类型陷阱
| # | 陷阱 | 描述 | 正解 |
|---|---|---|---|
| 1 | 有符号/无符号比较 | int(-1) < size_t(0) 为 false | std::cmp_less(C++20)/ 显式转换 |
| 2 | vector<bool> 不是容器 | auto b = v[0]; 是 proxy,不是引用 | 用 deque<bool> 或显式 bool |
| 3 | 整数提升 | uint16_t a=1, b=2; auto c=a*b; 结果是 int | 注意 <cstdint> 和提升规则 |
| 4 | 构造 vs 函数声明 | Foo f(Bar()); 声明了一个函数 | Foo f{Bar()} / Foo f(Bar{}) |
# 13.3 性能陷阱
| # | 陷阱 | 描述 | 正解 |
|---|---|---|---|
| 1 | std::endl 频繁 flush | std::cout << x << std::endl; | std::cout << x << '\n'; |
| 2 | push_back 不 reserve | 反复 reallocate + copy | v.reserve(N); |
| 3 | 传值大对象 | void f(std::string s) 每次拷贝 | void f(const std::string& s) 或 string_view |
| 4 | return std::move(v) | 阻止 NRVO | return v; |
| 5 | map 遍历用 [] | for(...) map[k] 会插入新元素 | 用 find() 或 at() |
| 6 | 虚函数在热循环 | 每次调用都查 vtable | 用模板/CRTP 消除虚函数 |
上次更新: 2026/06/17, 09:06:19