基于 C++ 核心指南 (isocpp.github.io) 的 C++ 编码规范。在编写、评审或重构 C++ 代码时使用,以强制执行现代、安全且地道的实践。
67
82%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
源自 C++ 核心指南 (C++ Core Guidelines) 的现代 C++ (C++17/20/23) 综合编码规范。强制执行类型安全 (Type safety)、资源安全 (Resource safety)、不变性 (Immutability) 和清晰度。
enum vs enum class,原始指针 vs 智能指针)以下主题贯穿整个指南并构成基础:
const/constexpr;可变性应当是例外。不变性 (Immutability)| 规则 | 摘要 |
|---|---|
| P.1 | 直接在代码中表达思想 |
| P.3 | 表达意图 |
| P.4 | 理想情况下,程序应该是静态类型安全的 |
| P.5 | 优先考虑编译时检查而非运行时检查 |
| P.8 | 不要泄露任何资源 |
| P.10 | 优先考虑不可变数据而非可变数据 |
| I.1 | 使接口显式化 |
| I.2 | 避免使用非 const 的全局变量 |
| I.4 | 使接口具有精确且强类型的定义 |
| I.11 | 绝不通过原始指针或引用转移所有权 |
| I.23 | 保持较少的函数参数数量 |
// P.10 + I.4: 不可变的强类型接口
struct Temperature {
double kelvin;
};
Temperature boil(const Temperature& water);// 弱接口:所有权不明确,单位不明确
double boil(double* temp);
// 非 const 全局变量
int g_counter = 0; // 违反 I.2| 规则 | 摘要 |
|---|---|
| F.1 | 将有意义的操作打包成命名谨慎的函数 |
| F.2 | 函数应执行单一逻辑操作 |
| F.3 | 保持函数简短且简单 |
| F.4 | 如果函数可能在编译时求值,请将其声明为 constexpr |
| F.6 | 如果函数绝不抛出异常,请将其声明为 noexcept |
| F.8 | 优先选择纯函数 (Pure functions) |
| F.16 | 对于“输入”参数,低开销拷贝类型按值传递,其他类型按 const& 传递 |
| F.20 | 对于“输出”值,优先选择返回值而非输出参数 |
| F.21 | 若要返回多个“输出”值,优先返回结构体 |
| F.43 | 绝不返回指向局部对象的指针或引用 |
// 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); // 避免这样做// F.4 + F.8: 尽可能使用纯函数和 constexpr
constexpr int factorial(int n) noexcept {
return (n <= 1) ? 1 : n * factorial(n - 1);
}
static_assert(factorial(5) == 120);T&& (F.45)va_arg / C 风格变长参数 (F.55)const T,这会抑制移动语义 (F.49)| 规则 | 摘要 |
|---|---|
| C.2 | 如果存在不变式 (Invariant),使用 class;如果成员数据独立变化,使用 struct |
| C.9 | 最小化成员的公开暴露 |
| C.20 | 如果可以避免定义默认操作,则不要定义(零法则 Rule of Zero) |
| C.21 | 如果定义或 =delete 了任何拷贝/移动/析构函数,请处理所有五个(五法则 Rule of Five) |
| C.35 | 基类析构函数:要么是 public virtual,要么是 protected non-virtual |
| C.41 | 构造函数应创建一个完全初始化的对象 |
| C.46 | 将单参数构造函数声明为 explicit |
| C.67 | 多态类 (Polymorphic class) 应抑制公开的拷贝/移动 |
| C.128 | 虚函数:必须精确指定 virtual、override 或 final 中的一个 |
// C.20: 让编译器生成特殊成员
struct Employee {
std::string name;
std::string department;
int id;
// 无需析构函数、拷贝/移动构造函数或赋值运算符
};// 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_;
};// 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_;
};memset/memcpy (C.90)const 或引用,这会抑制移动/拷贝 (C.12)| 规则 | 摘要 |
|---|---|
| R.1 | 使用 RAII 自动管理资源 |
| R.3 | 原始指针 (T*) 是非所有权的 |
| R.5 | 优先选择作用域对象;不要不必要地在堆上分配 |
| R.10 | 避免使用 malloc()/free() |
| R.11 | 避免显式调用 new 和 delete |
| R.20 | 使用 unique_ptr 或 shared_ptr 表示所有权 |
| R.21 | 除非共享所有权,否则优先选择 unique_ptr 而非 shared_ptr |
| R.22 | 使用 make_shared() 创建 shared_ptr |
// 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());// 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_;
};new/delete (R.11)malloc()/free() (R.10)unique_ptr 足以胜任的情况下使用 shared_ptr (R.21)| 规则 | 摘要 |
|---|---|
| ES.5 | 保持较小的作用域 |
| ES.20 | 始终初始化对象 |
| ES.23 | 优先选择 {} 初始化语法 |
| ES.25 | 除非打算修改,否则将对象声明为 const 或 constexpr |
| ES.28 | 对 const 变量的复杂初始化使用 lambda |
| ES.45 | 避免幻数 (Magic constants);使用符号常量 |
| ES.46 | 避免收窄/有损的算术转换 |
| ES.47 | 使用 nullptr 而非 0 或 NULL |
| ES.48 | 避免强制类型转换 (Casts) |
| ES.50 | 不要转换掉 const 属性 |
// 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;
}();0 或 NULL 作为指针 (ES.47 —— 应使用 nullptr)static_cast、const_cast 等)const 属性 (ES.50)| 规则 | 摘要 |
|---|---|
| E.1 | 在设计早期制定错误处理策略 |
| E.2 | 当函数无法执行其分配的任务时抛出异常 |
| E.6 | 使用 RAII 防止泄露 |
| E.12 | 当不可能或不接受抛出异常时使用 noexcept |
| E.14 | 使用专门设计的用户定义类型作为异常 |
| E.15 | 按值抛出,按引用捕获 |
| E.16 | 析构函数、释放操作和 swap 绝不能失败 |
| E.17 | 不要试图在每个函数中捕获每个异常 |
// 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: 不要在此处捕获所有异常 —— 让非预期错误继续传播
}int 或字符串字面量 (E.14)errno)的错误处理 (E.28)| 规则 | 摘要 |
|---|---|
| Con.1 | 默认情况下,使对象不可变 |
| Con.2 | 默认情况下,使成员函数为 const |
| Con.3 | 默认情况下,传递指向 const 的指针和引用 |
| Con.4 | 对于构造后不改变的值使用 const |
| Con.5 | 对于编译时可计算的值使用 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;| 规则 | 摘要 |
|---|---|
| CP.2 | 避免数据竞态 (Data races) |
| CP.3 | 最小化显式共享可写数据 |
| CP.4 | 以任务 (Tasks) 而非线程 (Threads) 的方式思考 |
| CP.8 | 不要使用 volatile 进行同步 |
| CP.20 | 使用 RAII,绝不直接调用 lock()/unlock() |
| CP.21 | 使用 std::scoped_lock 获取多个互斥锁 |
| CP.22 | 持有锁时绝不调用未知代码 |
| CP.42 | 不要无条件等待 |
| CP.44 | 记得为你的 lock_guard 和 unique_lock 命名 |
| CP.100 | 除非绝对必要,否则不要使用无锁编程 (Lock-free programming) |
// 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_;
};// 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;
}volatile 进行同步 (CP.8 —— 它仅用于硬件 I/O)std::lock_guard<std::mutex>(m); 会立即销毁 (CP.44)| 规则 | 摘要 |
|---|---|
| T.1 | 使用模板提升抽象层级 |
| T.2 | 使用模板为多种参数类型表达算法 |
| T.10 | 为所有模板参数指定概念 (Concepts) |
| T.11 | 尽可能使用标准概念 |
| T.13 | 简单概念优先使用简写表示法 |
| T.43 | 优先选择 using 而非 typedef |
| T.120 | 仅在确实需要时使用模板元编程 (Template metaprogramming) |
| T.144 | 不要特化函数模板(应使用重载) |
#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);constexpr 足以胜任时使用模板元编程 (T.120)typedef 而非 using (T.43)| 规则 | 摘要 |
|---|---|
| SL.1 | 尽可能使用库 |
| SL.2 | 优先选择标准库而非其他库 |
| SL.con.1 | 优先选择 std::array 或 std::vector 而非 C 数组 |
| SL.con.2 | 默认优先选择 std::vector |
| SL.str.1 | 使用 std::string 拥有字符序列 |
| SL.str.2 | 使用 std::string_view 引用字符序列 |
| SL.io.50 | 避免使用 endl(使用 '\n' —— endl 会强制刷新缓冲区) |
// 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';| 规则 | 摘要 |
|---|---|
| Enum.1 | 优先选择枚举而非宏 |
| Enum.3 | 优先选择 enum class 而非普通 enum |
| Enum.5 | 不要对枚举成员使用 全大写 (ALL_CAPS) 命名 |
| Enum.6 | 避免使用匿名枚举 |
// 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| 规则 | 摘要 |
|---|---|
| SF.1 | 代码文件使用 .cpp,接口文件使用 .h |
| SF.7 | 不要在头文件的全局作用域编写 using namespace |
| SF.8 | 为所有 .h 文件使用 #include 保护 |
| SF.11 | 头文件应当是自包含的 (Self-contained) |
| NL.5 | 避免在名称中编码类型信息(不使用匈牙利命名法) |
| NL.8 | 使用一致的命名风格 |
| NL.9 | 仅对宏名称使用 全大写 (ALL_CAPS) |
| NL.10 | 优先选择 下划线风格 (underscore_style) 命名 |
// 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// 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_projectusing namespace std; (SF.7)strName、iCount (NL.5)| 规则 | 摘要 |
|---|---|
| Per.1 | 不要无理由地进行优化 |
| Per.2 | 不要进行过早优化 |
| Per.6 | 没有测量,不要对性能下结论 |
| Per.7 | 旨在支持优化的设计 |
| Per.10 | 依赖静态类型系统 |
| Per.11 | 将计算从运行时移至编译时 |
| Per.19 | 以可预测的方式访问内存 |
// 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; // 不良实践:指针追踪在标记 C++ 工作完成之前:
new/delete —— 使用智能指针或 RAII (R.11)const/constexpr (Con.1, ES.25)const (Con.2)enum class 而非普通 enum (Enum.3)nullptr 而非 0/NULL (ES.47)explicit (C.46)using namespace (SF.7)scoped_lock/lock_guard) (CP.20)'\n' 而非 std::endl (SL.io.50)dfbf946
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.