《Effective STL》读书摘要
基本信息
- 作者:Scott Meyers(Effective C++系列作者)
- 来源:云盘 /田浩然上传的资料/电子书/C_C++/[计算机科学经典着作].Addison.Wesley.Effective.Stl.50.Specific.Ways.To.Improve.Your.Use.Of.Stl.pdf
- 页数:198页
- 年份:2001年
- 类别:编程书籍 / C++ / STL最佳实践
核心框架
本书以50条Item的形式,逐一讲解STL使用中常见的陷阱和最佳实践。每条Item结构为:问题 → 解释 → 解决方案 → 代码示例。
全书按主题分为6章:
- Containers(容器通用)
- vector and string
- Associative Containers(关联容器)
- Iterators
- Algorithms
- Functors、Function Objects等
- Programming with the STL
核心Item精要
容器通用(Items 1-12)
| Item | 要点 |
|---|
| 1 | 谨慎选择容器:区分连续内存容器(vector/deque/string)与节点容器(list/set/map)。考虑:插入位置、排序需求、标准合规、迭代器类型、元素移动、内存兼容性、查找速度、引用计数、事务语义、迭代器失效条件 |
| 2 | 警惕容器无关代码的幻觉:不同容器接口差异很大,强行通用化会丧失性能和表达能力 |
| 3 | 让拷贝廉价而正确:容器中的对象必须能正确拷贝,否则行为未定义 |
| 4 | 用empty()代替检查size()==0:对所有容器都高效;某些容器的size()是O(n) |
| 5 | 优先使用范围成员函数:insert(pos, first, last) 比逐个insert更高效 |
| 6 | 警惕C++最烦人的解析:Foo x(); 被解析为函数声明,非对象定义 |
| 7 | 容器存new的指针时记得delete:容器销毁时不会自动delete指针指向的对象 |
| 8 | 绝不在容器中放auto_ptr:auto_ptr的拷贝语义与容器要求冲突 |
| 9 | 谨慎选择删除方式:erase返回新迭代器(sequence容器)或void(associative容器) |
| 10 | 了解allocator约定与限制:不同容器的allocator可能不等价 |
| 11 | 理解自定义allocator的合法用途:内存池、调试追踪等 |
| 12 | 对STL容器线程安全保持现实期望:只有”同一容器上的不同读操作”才线程安全 |
vector与string(Items 13-18)
| Item | 要点 |
|---|
| 13 | 优先用vector/string代替动态数组:自动内存管理、异常安全 |
| 14 | 用reserve()避免不必要的重新分配:预估容量,提前reserve |
| 15 | 了解string实现的变体:SSO(短字符串优化)、引用计数等 |
| 16 | 知道如何把vector/string数据传给遗留API:&v[0] 或 v.data() |
| 17 | 用”swap技巧”修剪多余容量:vector<T>(v).swap(v) |
| 18 | 避免使用vector:它是个特化,行为不像普通vector |
关联容器(Items 19-25)
| Item | 要点 |
|---|
| 19 | 理解相等与等价的区别:== vs operator<(等价) |
| 20 | 为指针的关联容器指定比较类型:默认按地址比较,通常不是你想要的 |
| 21 | 比较函数对相等值必须返回false:违反此规则导致未定义行为 |
| 22 | 避免在set/multiset中就地修改key:会破坏容器排序,导致未定义行为 |
| 23 | 考虑用排序vector替代关联容器:内存 locality 更好,遍历更快 |
| 24 | 效率敏感时用insert而非operator[]:operator[] 在无元素时会插入默认值 |
| 25 | 熟悉非标准哈希容器:hash_set/hash_map等(SGI扩展,非C++标准) |
迭代器(Items 26-29)
| Item | 要点 |
|---|
| 26 | 优先用iterator而非const_iterator:需要时可以隐式转换 |
| 27 | 用distance和advance转换const_iterator:const_iterator不能直接加减 |
| 28 | 理解reverse_iterator的base():base()指向”当前元素的下一个” |
| 29 | 考虑用istreambuf_iterator做逐字符输入:比istream_iterator更高效 |
算法(Items 30-37)
| Item | 要点 |
|---|
| 30 | 确保目标范围足够大:用back_inserter等插入迭代器避免越界 |
| 31 | 了解你的排序选项:sort/stable_sort/partial_sort/nth_element各有适用场景 |
| 32 | remove类算法后要接erase:remove不真正删除元素,只”移走” |
| 33 | 对指针容器慎用remove类算法:remove的是指针值,不是所指对象 |
| 34 | 注意哪些算法要求有序范围:binary_search/upper_bound/lower_bound/equal_range |
| 35 | 用mismatch或lexicographical_compare做大小写不敏感比较 |
| 36 | 理解copy_if的正确实现:C++98中没有copy_if,需自己实现 |
| 37 | 用accumulate或for_each做范围汇总 |
Functors与函数对象(Items 38-42)
| Item | 要点 |
|---|
| 38 | 设计functor类时按值传递:避免引用悬空 |
| 39 | 使谓词为纯函数:不修改状态,保证可重入性 |
| 40 | 使functor类可适配:提供typedef使算法能正确使用 |
| 41 | 理解ptr_fun/mem_fun/mem_fun_ref的原因:将函数指针/成员函数转为functor |
| 42 | 确保less意味着operator<:否则关联容器行为未定义 |
STL编程(Items 43-50)
| Item | 要点 |
|---|
| 43 | 优先用算法调用代替手写循环:更简洁、更高效、更少bug |
| 44 | 优先用成员函数代替同名算法:list::sort() 比 std::sort() 更高效 |
| 45 | 区分count/find/binary_search/lower_bound/upper_bound/equal_range |
| 46 | 考虑用函数对象代替函数作为算法参数:可携带状态 |
| 47 | 避免写出难以阅读的代码:过度泛化是维护噩梦 |
| 48 | 始终包含正确的头文件:<algorithm> 不等于 <vector> |
| 49 | 学会解读STL相关的编译器诊断:用typedef展开模板类型 |
| 50 | 熟悉STL相关网站:SGI STL、STLport、Boost |
核心思维模型
连续内存容器 vs 节点容器
| 特性 | 连续内存(vector/deque/string) | 节点(list/set/map) |
|---|
| 内存布局 | 连续块 | 分散节点 |
| 插入/删除中间 | 移动元素,O(n) | 改指针,O(1) |
| 迭代器失效 | 可能全部失效 | 仅指向被删元素的失效 |
| 缓存友好 | 好 | 差 |
| 随机访问 | O(1) | O(n) |
remove-erase惯用法
// 错误:remove不真正删除
std::remove(v.begin(), v.end(), value);
// 正确:remove + erase
v.erase(std::remove(v.begin(), v.end(), value), v.end());
容器选择决策树
需要随机访问? ──是──→ vector
│
否
↓
需要在中间频繁插入/删除? ──是──→ list
│
否
↓
需要在首尾频繁插入/删除? ──是──→ deque
│
否
↓
需要排序/去重/快速查找? ──是──→ set/map
│
否
↓
需要哈希查找? ──是──→ unordered_set/unordered_map(C++11)
与《Essential C++》的关系
- Essential C++:系统性入门,理解C++语言特性
- Effective STL:进阶最佳实践,理解STL的正确使用方式
- 建议先读Essential C++第3章(泛型编程),再精读Effective STL
可行动点
- 立即实践:把所有手写循环替换为算法调用(Item 43)
- 代码审查重点:检查是否有
vector<bool>、auto_ptr、未接erase的remove(Item 7/8/32)
- 性能优化:对vector提前reserve(Item 14),对list用成员sort(Item 44)
- 编译器诊断:学会用typedef展开模板类型,快速定位bug(Item 49)