Appearance
C++ 设计思想专题
C++ 的机制看似零散,背后其实是少数几条哲学:资源绑定生命周期、失败要原子、抽象不能有代价、正确性尽量前置到编译期、性能是设计出来的、解耦以便演进。本专题把各专题里的"点"提升成"线",专治"为什么这么设计"的追问。
六大思想簇(总览)
| 思想簇 | 一句话内核 | 子页 |
|---|---|---|
| ① 资源与生命周期 | 资源随对象生、随对象亡,所有权清晰 | lifetime.md |
| ② 失败要原子 | 一个操作要么完成、要么像没发生 | atomic-failure.md |
| ③ 抽象不能有代价 | 不为没用到的特性付费,成本可预测 | zero-overhead.md |
| ④ 把正确性前置 | 编译期抓错 > 运行期抓错 | correctness-first.md |
| ⑤ 性能是设计出来的 | 局部性、少拷贝、摊还,先测量再优化 | performance-design.md |
| ⑥ 工程与演进 | 接口/实现分离、组合优于继承、兼容意识 | engineering.md |
每篇都在"一句话"之外给出展开解释、折叠小例子,并回链到已有专题(stl / memory / concurrency / modern-cpp / …),方便你顺着"思想 → 机制 → 考点"一路追下去。
高频自测(含答案)
1. C++ 为什么不需要 GC?
答案
靠 RAII + 确定性析构:栈对象离开作用域必然析构,资源被自动、确定地回收,而且能管内存之外的资源(锁/文件/socket)。GC 只管内存、回收时机不确定。详见 lifetime.md、modern-cpp/raii.md。
2. 什么是强异常保证?为什么 vector 扩容有时是拷贝而非移动?
答案
强保证 = 操作失败时回滚到"像没发生"。扩容搬迁要么全成、要么全不成:拷贝时源对象完好、可回滚;移动时源已被掏空、不可回滚。所以"移动可能抛 且 类型可拷贝"时退化为拷贝。详见 atomic-failure.md、stl/vector-realloc.md。
3. 什么是零开销抽象?std::function 违反了吗?
答案
"不为没用到的特性付费":抽象在运行时不应带来额外成本(模板/内联/constexpr 常被优化到与手写等价)。std::function 用类型擦除换统一接口,有间接调用开销——这是有意的、可见的代价,不算"偷偷付费"。详见 zero-overhead.md。
4. 为什么建议给移动构造标 noexcept、给接口标 const、给构造标 explicit?
答案
都是把信息前置:noexcept 让 vector 敢移动(保性能与异常安全);const 表达只读、让编译器帮你查错;explicit 禁止危险的隐式转换。把约束写进类型,正确性问题尽量在编译期暴露。详见 correctness-first.md。
5. 什么是 copy-and-swap?它同时解决哪两个问题?
答案
先构造副本、改好后再 swap:既拿到强异常保证(副本失败不影响原对象),又用一次 swap 统一实现拷贝赋值(避免自赋值/资源泄漏)。详见 atomic-failure.md。
6. 为什么 STL/现代 C++ 推崇值语义与移动,而非到处用指针?
答案
值语义让拷贝/移动成本显式可见、无隐藏共享,配合移动语义可避免昂贵深拷贝。指针/引用引入别名与生命周期问题。详见 lifetime.md、modern-cpp/move-semantics.md。
7. 为什么说"性能是设计出来的"?举个例子。
答案
数据布局与访问模式常比算法常数更决定性能:缓存友好、SoA vs AoS、少拷贝、惰性求值、摊还(vector 2× 扩容)都是设计决策。详见 performance-design.md、lowlevel/measure-timing.md。
8. 组合优于继承,为什么?
答案
继承是"是一个"、耦合强、易碎基类问题;组合是"有一个/用这个",只依赖接口、可替换、易测试。只在需要运行时多态/接口复用时才用继承。详见 engineering.md、oop/index.md。
9. Pimpl 除了编译加速还有什么好处?
答案
把实现藏在指针后:减少头文件依赖(编译防火墙)、稳定 ABI,且析构在实现文件中定义,顺带获得强异常安全(也便于实现 Rule of Zero)。详见 engineering.md、build/index.md。
10. 静态多态(模板/CRTP)与动态多态(虚函数)怎么选?
答案
要零开销、编译期解析、无需运行期扩展 → 静态多态;要运行期可替换、接口稳定、异构容器 → 动态多态(虚函数)。详见 zero-overhead.md、oop/vtable.md。
11. 摊还分析是什么?vector::push_back 为什么是均摊 O(1)?
答案
单次操作可能很贵(扩容搬迁 O(n)),但均摊到整体每次接近 O(1):容量翻倍使 n 次 push_back 的总搬移量约 2n。详见 performance-design.md、stl/vector-realloc.md。
12. 为什么说"对称性"是资源管理的核心原则?
答案
凡成对出现的操作(构造/析构、加锁/解锁、open/close、acquire/release)都必须成对完成;RAII 正是用对象生命周期强制这种对称,杜绝异常路径漏做一半。详见 lifetime.md、concurrency/index.md。
子文档
| 文件 | 内容 |
|---|---|
lifetime.md | 资源与生命周期:RAII、所有权、确定性析构、Rule of 0/3/5、悬垂 |
atomic-failure.md | 失败要原子:全有或全无、强异常保证、copy-and-swap、noexcept |
zero-overhead.md | 零开销抽象、值语义、编译期计算、concepts、类型擦除、静态 vs 动态多态 |
correctness-first.md | const 正确性、强类型、fail-fast、契约、错误处理策略 |
performance-design.md | 局部性、DOD、少拷贝、惰性、摊还、假共享、先测量 |
engineering.md | 接口/实现分离、Pimpl、组合优于继承、DRY、ABI |
相关
- Modern C++ 设计模式与最佳实践:
patterns/ - 智能指针与所有权:
smart-pointers/ - 内存管理与布局:
memory/ - 并发与同步:
concurrency/ - 现代特性与移动语义:
modern-cpp/ - 性能与底层:
performance/、lowlevel/