ECC C++ 模式指南:从 RAII 到错误处理的现代 C++ 实战规范
【免费下载链接】ECCThe agent harness performance optimization system. Skills, instincts, memory, security, and research-first development for Claude Code, Codex, Opencode, Cursor and beyond.项目地址: https://gitcode.com/GitHub_Trending/ev/ECC
本文是 ECC(The agent harness performance optimization system)规则体系中的 C++ 模式(patterns)专项指南,聚焦于 C++ 开发中最核心的四大设计主题:RAII 资源管理、五法则/零法则、值语义与错误处理。结合 ECC 仓库内 rules/cpp/patterns.md 与 skills/cpp-coding-standards/SKILL.md 的源码级规范,读者将掌握可落地的现代 C++(C++17/20/23)编码模式,并理解这些规则在 Agent 辅助编码/评审场景中如何被触发与强制执行。
规则文件的定位:语言层如何扩展通用层
在 ECC 的规则体系中,规则被组织为common 通用层 + 语言专属层两层结构(详见 rules/README.md):
rules/ ├── common/ # 语言无关原则(始终安装) │ └── patterns.md └── cpp/ # C++ 专属规则 ├── patterns.md # ← 本文主题 ├── coding-style.md ├── hooks.md ├── security.md └── testing.mdrules/cpp/patterns.md的职责非常明确:它扩展而非重复通用层的 rules/common/patterns.md(后者聚焦于骨架项目选择、Repository 模式、API 响应封装等语言无关设计模式),用 C++ 特有的内容进行补充。文档开头明确声明:"This file extends common/patterns.md with C++ specific content."
按照 ECC 的规则优先级设计,当语言专属规则与通用规则冲突时,语言专属规则优先(类似 CSS 特异性或.gitignore优先级),这保证了 C++ 习惯用法不会被通用模式覆盖。
规则触发条件(Frontmatter paths)
每个语言规则文件头部都带有 frontmatterpaths元数据,定义了该规则在 Agent 工作流中自动生效的文件匹配范围:
paths: - "**/*.cpp" - "**/*.hpp" - "**/*.cc" - "**/*.hh" - "**/*.cxx" - "**/*.h" - "**/CMakeLists.txt"这意味着当 Agent 在编写、评审或重构任何.cpp/.hpp/.cc/.hh/.cxx/.h源码或CMakeLists.txt构建脚本时,本模式规则都会被加载并作为评审依据。
RAII:将资源生命周期绑定到对象生命周期
RAII(Resource Acquisition Is Initialization,资源获取即初始化)是 C++ 区别于其他语言最核心的资源管理哲学。其本质是:资源的获取发生在对象构造时,资源的释放发生在对象析构时,从而让编译器自动保证资源的释放,杜绝泄漏。
rules/cpp/patterns.md给出的最小范例是一个文件句柄封装:
class FileHandle { public: explicit FileHandle(const std::string& path) : file_(std::fopen(path.c_str(), "r")) {} ~FileHandle() { if (file_) std::fclose(file_); } FileHandle(const FileHandle&) = delete; FileHandle& operator=(const FileHandle&) = delete; private: std::FILE* file_; };注意几个关键设计点:
explicit单参数构造函数:防止std::string到FileHandle的隐式转换,对应 C++ Core Guidelines 的C.46规则;- 析构函数负责释放:无论控制流如何(正常返回、抛出异常、
break/continue),析构函数都会被调用,资源必然释放; - 删除拷贝构造/拷贝赋值:
FILE*是裸指针,浅拷贝会导致 double-free,因此显式= delete禁止拷贝,把类设计成 move-only(可配合std::exchange实现移动语义)。
源码级深化:完整版 RAII 实现
在 skills/cpp-coding-standards/SKILL.md 的R.* 资源管理章节中,给出了更完整的 RAII 类实现(对应规则R.1:用 RAII 自动管理资源):
// 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_; };相比基础版,完整版补齐了资源获取失败时的异常抛出(throw std::runtime_error)与移动语义(利用std::exchange转移句柄并置空源对象)。这正对应异常安全与所有权转移的完整闭环。
在E.* 错误处理部分,RAII 也被列为防泄漏的基石(E.6:Use RAII to prevent leaks),在CP.* 并发部分,RAII 同样延伸到锁管理(CP.20:Use RAII, never plainlock()/unlock()),可见 RAII 是贯穿整个 C++ Core Guidelines 的横切原则(对应 Guideline 编号 P.8、R.1、E.6、CP.20)。
智能指针:RAII 的现代形态
规则文件与 skill 都强调:现代 C++ 应使用智能指针替代裸new/delete(R.11),并遵循以下取舍(R.20/R.21/R.22):
// R.11 + R.20 + R.21: RAII with smart pointers 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());std::unique_ptr是默认选择:独占所有权,零额外开销;std::shared_ptr仅在真正需要共享所有权时使用(R.21);- 裸指针只允许作为非拥有的观察者(R.3),绝不传递所有权(I.11);
- 优先
std::make_unique/std::make_shared,避免裸new与异常安全风险(R.22,且make_shared减少一次内存分配)。
零法则与五法则:特殊成员函数的管理策略
规则文件定义了 C++ 中最容易被忽视的类设计决策:
- 零法则(Rule of Zero):优先使用不需要自定义析构函数、拷贝/移动构造函数或赋值运算符的类。让编译器生成正确的特殊成员函数;
- 五法则(Rule of Five):如果必须定义析构函数、拷贝构造函数、拷贝赋值、移动构造函数、移动赋值中的任意一个,就必须全部定义五个。
对应 C++ Core Guidelines 的C.20(能避免定义默认操作就避免,即零法则)与C.21(定义了任意拷贝/移动/析构,就必须处理全部五个)。
零法则示例
// C.20: Let the compiler generate special members struct Employee { std::string name; std::string department; int id; // 无需析构函数、拷贝/移动构造函数、赋值运算符 };当成员本身都是 RAII 类型(如std::string、std::vector)且没有需要手工管理的资源时,编译器生成的默认操作完全正确,手写特殊成员函数反而是引入 bug 的源头(例如漏写移动操作导致拷贝开销、手写析构破坏移动语义)。
五法则示例
// 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_; };这个Buffer类完整呈现了五法则的意图:
- 深拷贝:拷贝构造与拷贝赋值都通过
std::make_unique重新分配并std::copy_n复制数据(拷贝赋值采用 copy-and-swap 风格,先构造临时再移动赋值,天然具备强异常安全); - 移动:
= default即可,因为unique_ptr本身就是可移动的; noexcept移动操作:让std::vector<Buffer>扩容时能走移动而非拷贝。
相关反模式(来自 skill)
- 在构造函数/析构函数中调用虚函数(C.82);
- 对非平凡类型使用
memset/memcpy(C.90); - 数据成员声明为
const或引用,从而禁用移动/拷贝(C.12); - 虚基类与重写函数提供不同的默认参数(C.140)。
另外注意,在多态类中还应抑制公开拷贝/移动(C.67),基类析构函数要么是public virtual,要么是protected non-virtual(C.35),避免通过基类指针删除派生类对象时发生未定义行为。
值语义:传参、返回与移动的黄金法则
规则文件给出值语义的四条指导原则:
- 小/平凡类型按值传递——例如
int、double、std::pair这类廉价拷贝类型; - 大型类型按
const&传递——例如std::string、std::vector这类昂贵拷贝类型; - 按值返回(依赖 RVO/NRVO 消除拷贝);
- 对 sink 参数(接收后即被消耗的参数)使用移动语义。
源码级深化:参数传递(F.16)与返回(F.20/F.21)
skill 中 F.* 函数章节给出了可复制的完整范式:
// F.16: 廉价类型按值,昂贵类型按 const& void print(int x); // 廉价:按值 void analyze(const std::string& data); // 昂贵:按 const& void transform(std::string s); // sink:按值(将会移动) // 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); // avoid this要点解读:
- sink 参数按值传递:调用方传
std::move(s)时,参数直接移动构造,避免额外拷贝;传左值时则拷贝,语义清晰且不会悬垂; - 优先返回值而非输出参数(F.20):现代编译器通过 RVO(Return Value Optimization)保证按值返回零拷贝;返回
std::string、std::vector这类自带移动语义的类型,移动开销也极小; - 多返回值用结构体(F.21):比输出参数更清晰,也避免参数顺序错误;
- 纯函数与 constexpr(F.4/F.8):可能编译期求值的函数声明为
constexpr:
// 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);值语义相关反模式
- 返回
T&&(F.45)——悬垂引用风险; - 返回
const T(F.49)——抑制移动语义; - 使用 C 风格变参
va_arg(F.55); - 在传递给其他线程的 lambda 中按引用捕获(F.53)。
错误处理:异常、optional 与 expected 的三层分工
规则文件给出了 C++ 错误处理的分层策略,这是现代 C++ 最重要的设计决策之一:
- 异常情况(exceptional conditions)使用异常(exceptions)——函数无法完成其任务时抛出;
- 可能不存在的值使用
std::optional——例如查找结果、解析可选字段,返回值"可能没有"但并非错误; - 预期失败(expected failures)使用
std::expected(C++23)或结果类型——例如网络请求失败、文件解析失败这类"调用方应主动处理"的场景,用错误码/结果类型显式返回。
这一分层与 docs/ja-JP/rules/cpp/coding-style.md 中"现代 C++ 优先于 C 风格语法"的导向一致:C 语言的errno+ 返回值约定(E.28 反模式)被彻底替代。
源码级深化:异常层级设计(E.14/E.15/E.17)
skill 的 E.* 章节提供了符合规范的异常类型设计:
// 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: 不要在这里捕获一切——让意外错误向上传播 }设计要点:
- 异常类型有明确意图(E.14):
NetworkError携带status_code,调用方可以按具体类型分别处理; - 按值抛出、按引用捕获(E.15):避免切片(slicing)风险,
catch (const NetworkError&)优先于更宽的catch (const AppError&); - 不吞异常(E.17):不要在每个函数里
catch (...)空吞错误;无法处理的错误应传播给上层; noexcept的正确使用(E.12/E.16):析构函数、释放操作、swap 必须noexcept且永不失败。
错误处理反模式清单
- 抛出内建类型(如
int、字符串字面量)作为异常(E.14); - 按值捕获(存在切片风险)(E.15);
- 空
catch块静默吞掉错误; - 用异常做流程控制(E.3);
- 依赖
errno等全局状态做错误处理(E.28)。
模式规则如何融入 ECC 的 C++ 开发闭环
C++ 模式文件并非孤立存在,它是 ECC C++ 规则族(patterns / coding-style / hooks / security / testing)的一员,与技能层cpp-coding-standards协同工作:
- rules/cpp/coding-style.md:补充了资源管理(禁用手工
new/delete、make_unique/make_shared优先)、命名规范(类型 PascalCase、函数 snake_case/camelCase、常量 kPascalCase 或 UPPER_SNAKE_CASE、成员snake_case_或m_前缀)、格式化(强制clang-format -i <file>,杜绝风格争论)等约定; cpp-coding-standards技能(skills/cpp-coding-standards/SKILL.md):提供基于 C++ Core Guidelines 的完整规则矩阵,覆盖 Philosophy(P.*)、Interfaces(I.*)、Functions(F.*)、Classes(C.*)、Resources(R.*)、Expressions & Statements(ES.*)、Error Handling(E.*)、Constants & Immutability(Con.*)、Concurrency(CP.*)、Templates(T.*)、Standard Library(SL.*)、Enumerations(Enum.*)、Source Files & Naming(SF.*/NL.*)、Performance(Per.*)共 14 大领域,并附随一个完成前检查清单(Quick Reference Checklist)——这是 Agent 评审 C++ 代码时的可直接引用的核对表;- 规则层与技能层的关系正如 rules/README.md 所述:Rules 告诉你"做什么"(标准与清单),Skills 告诉你"怎么做"(可操作的深度参考资料)。
安装与使用方式
如果你需要在本地 Claude Code / Codex 等 Agent 环境中启用这套规则,可以按 rules/README.md 的方式安装(以用户级为例):
mkdir -p ~/.claude/rules/ecc # 安装通用规则(所有项目必需) cp -r rules/common ~/.claude/rules/ecc/ # 安装语言专属规则(按项目技术栈选择) cp -r rules/cpp ~/.claude/rules/ecc/注意:必须整目录拷贝而非/*展开,因为 common 与语言目录存在同名文件(如patterns.md),扁平化会导致语言专属文件覆盖通用规则,并破坏语言文件中对../common/的相对引用。
实战速查:C++ 模式规则要点一览
| 主题 | 核心要点 | 对应 Guideline |
|---|---|---|
| RAII | 资源生命周期绑定对象生命周期,析构自动释放 | P.8, R.1, E.6, CP.20 |
| 智能指针 | unique_ptr默认,shared_ptr按需,裸指针仅观察 | R.11, R.20, R.21, R.22 |
| 零法则 | 无自定义资源管理的类,让编译器生成特殊成员 | C.20 |
| 五法则 | 定义其一则五者皆备 | C.21 |
| 值语义 | 小类型按值、大类型const&、按值返回、sink 移动 | F.16, F.20, F.21 |
| 异常 | 仅异常情况抛出,按值抛、按引用捕、不空吞 | E.2, E.14, E.15, E.17 |
| 可选值 | std::optional表示"可能不存在" | — |
| 预期失败 | std::expected(C++23)或结果类型显式返回 | — |
这套模式规则是 Agent 在编写、评审与重构 C++ 代码时的"硬性基准":RAII 杜绝资源泄漏,零/五法则杜绝特殊成员函数陷阱,值语义杜绝无谓拷贝与悬垂引用,错误处理分层则让失败路径清晰可预测。结合cpp-coding-standards技能中的完整规则矩阵与检查清单,即可在现代 C++ 项目中系统化落地这些经过 C++ Core Guidelines 验证的实践。
【免费下载链接】ECCThe agent harness performance optimization system. Skills, instincts, memory, security, and research-first development for Claude Code, Codex, Opencode, Cursor and beyond.项目地址: https://gitcode.com/GitHub_Trending/ev/ECC
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考