Install any skill in seconds. Free to start, no credit card required.
Get Started Free →基于 C++ 核心指南 (isocpp.github.io) 的 C++ 编码规范。在编写、评审或重构 C++ 代码时使用,以强制执行现代、安全且地道的实践。
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-04 | ✗→✓ | ▲ Improved | 275% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 367% | 0% |
| case-19 | ✓→✗ | ▼ Worse | 263% | 0% |
| case-05 | ✓→✓ | = Same ✓ | 342% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 372% | 0% |
源自 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 | 保持较少的函数参数数量 |
cpp// P.10 + I.4: 不可变的强类型接口 struct Temperature { double kelvin; }; Temperature boil(const Temperature& water);
cpp// 弱接口:所有权不明确,单位不明确 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 | 绝不返回指向局部对象的指针或引用 |
cpp// 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); // 避免这样做
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);
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 中的一个 |
cpp// C.20: 让编译器生成特殊成员 struct Employee { std::string name; std::string department; int id; // 无需析构函数、拷贝/移动构造函数或赋值运算符 };
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_; };
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_; };
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 |
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());
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_; };
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 属性 |
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: 用于复杂 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 | 不要试图在每个函数中捕获每个异常 |
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("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 |
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;
| 规则 | 摘要 | |------|---------| | 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) |
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_; };
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; }
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 | 不要特化函数模板(应使用重载) |
cpp#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 会强制刷新缓冲区) |
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 << "result: " << value << '\n';
| 规则 | 摘要 | |------|---------| | Enum.1 | 优先选择枚举而非宏 | | Enum.3 | 优先选择 enum class 而非普通 enum | | Enum.5 | 不要对枚举成员使用 全大写 (ALL_CAPS) 命名 | | 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 —— 应使用 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) 命名 |
cpp// 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
cpp// 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
using namespace std; (SF.7)strName、iCount (NL.5)| 规则 | 摘要 | |------|---------| | Per.1 | 不要无理由地进行优化 | | Per.2 | 不要进行过早优化 | | Per.6 | 没有测量,不要对性能下结论 | | Per.7 | 旨在支持优化的设计 | | Per.10 | 依赖静态类型系统 | | Per.11 | 将计算从运行时移至编译时 | | Per.19 | 以可预测的方式访问内存 |
cpp// 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)Other measured skills in the registry, with their headline benchmark lift.