Effective STL — 50条有效使用STL的经验

作者: Scott Meyers
云盘路径: /books/c++/effective c++/Effective STL.pdf
文件大小: 2.06MB
页数: 200页
处理日期: 2026-09-03
处理方法: pdf-inspector文本提取


核心要点

Scott Meyers的”Effective”系列第三部,继《Effective C++》和《More Effective C++》之后,专注STL最佳实践。全书50条Item,涵盖容器选择、迭代器、算法、函数对象四大主题。

章节结构与核心Item

一、容器(Containers)— Item 1-12

Item 1: 谨慎选择容器

  • 区分连续内存容器(vector, string, deque)与节点容器(list, set, map)
  • 选择容器时考虑:随机访问需求、排序需求、标准兼容性、迭代器类型、元素移动成本、C布局兼容、查找速度、引用计数、事务语义、迭代器失效规则
  • 没有”默认容器”,vector不是万能的

Item 2: 警惕容器无关代码的幻觉

  • 不同容器有本质差异(迭代器类型、失效规则、性能特征)
  • 试图写通用容器代码会丧失几乎所有优势
  • 用typedef封装而非抽象化,便于未来替换

Item 3: 容器中的对象拷贝要廉价且正确

  • 容器存的是拷贝而非原件(copy in, copy out)
  • 继承体系中存入基类容器会导致slicing
  • 解决方案:存指针(配合智能指针)

Item 4: 用empty()代替size()==0

  • empty()对所有标准容器都是O(1)
  • 某些list实现的size()是O(n)

Item 5: 优先使用范围成员函数

  • assign, range insert, range erase比单元素版本更高效
  • 减少函数调用开销、减少元素移动次数、减少内存重分配

Item 6: 警惕C++最烦人的解析

  • Widget w(); 声明的是返回Widget的函数,不是对象
  • 用括号包裹参数可强制解析为对象构造

Item 7: 容器存newed指针时记得delete

  • 容器析构不delete指针
  • 用for_each+DeleteObject函子或智能指针

Item 8: 永远不要创建auto_ptr容器

  • auto_ptr拷贝时转移所有权,破坏容器不变量
  • 排序可能将元素设为NULL
  • C++标准禁止auto_ptr容器

Item 9: 谨慎选择擦除选项

  • vector/deque/string: erase-remove惯用法
  • list: list::remove/remove_if
  • 关联容器: erase成员函数
  • 自定义擦除时注意迭代器失效规则

Item 10: 了解分配器的约定与限制

  • allocator::pointer/reference typedef常被实现忽略
  • 同类型分配器被认为总是相等
  • 不同容器不能共享分配器

Item 11: 理解自定义分配器的合法用途

  • 检测错误、收集统计、共享内存、空间优化
  • 大多数情况不需要自定义分配器

Item 12: 对STL容器的线程安全保持现实期望

  • 只读并发是安全的
  • 写入并发需要外部同步
  • 同一容器不允许读写并发

二、vector与string — Item 13-18

Item 13: 优先使用vector和string代替动态数组

  • 自动内存管理、size跟踪、复制语义
  • 动态数组无法传递size信息

Item 14: 用reserve避免不必要的重新分配

  • vector自动倍增容量(通常×2)
  • reserve可减少重分配次数

Item 15: 了解string实现的变体

  • 大多数实现使用引用计数(Copy-on-Write)
  • 引用计数导致const correctness问题
  • vector可替代string避免此问题

Item 16: 将vector和string数据传递给遗留API

  • vector数据连续存储,可用&v[0]传递
  • string数据连续存储,但C++98不保证(C++11保证)

Item 17: 用swap技巧修剪多余容量

  • vector无shrink_to_fit
  • 用swap临时vector可释放多余容量

Item 18: 避免使用vector

  • 不是真正的容器(特殊化)
  • 不返回bool&,返回代理对象
  • 用bitset或vector替代

三、关联容器 — Item 19-25

Item 19: 理解相等性与等价性的区别

  • 相等性:ab且ba
  • 等价性:!comp(a,b) && !comp(b,a)
  • 关联容器使用等价性判断

Item 20: 为指针关联容器指定比较类型

  • map<int*>默认按指针值排序而非所指对象
  • 需提供自定义比较器

Item 21: 比较函数对相等值必须返回false

  • 违反此规则导致未定义行为

Item 22: 避免在set/multiset中就地修改键

  • 修改键破坏容器排序不变量
  • 应erase后重新insert

Item 23: 考虑用排序vector替代关联容器

  • 内存效率更高
  • 迭代器失效更少
  • 二分搜索性能相当

Item 24: 效率敏感时谨慎选择map::operator[]与insert

  • operator[]在找不到时会插入默认值
  • insert不会改变容器

Item 25: 熟悉非标准哈希容器

  • hash_set/hash_map等提供O(1)查找
  • 非标准但广泛可用
  • SGI STLport Boost提供实现

四、迭代器 — Item 26-29

Item 26: 优先使用iterator而非const_iterator等

  • 类型推导困难
  • 代码更简洁

Item 27: 用distance和advance转换const_iterator

  • 将const_iterator转为iterator的标准做法

Item 28: 理解reverse_iterator的base迭代器

  • base()返回的是”下一个”位置
  • 用于关联容器erase时有陷阱

Item 29: 考虑用istreambuf_iterator逐字符输入

  • 绕过locale转换,效率更高

五、算法 — Item 30-37

Item 30: 确保目标范围足够大

  • back_inserter等插入迭代器可避免此问题
  • 但语义不同,需小心

Item 31: 了解你的排序选项

  • sort/stable_sort/partial_sort/nth_element适用场景
  • 排序算法要求随机访问迭代器

Item 32: remove-like算法后接erase才能真正删除

  • erase-remove惯用法
  • remove不改变容器size

Item 33: 对指针容器警惕remove-like算法

  • remove只移动指针不delete对象
  • 需自定义删除逻辑

Item 34: 注意哪些算法要求排序范围

  • binary_search/lower_bound/upper_bound/equal_range
  • 未排序范围调用导致未定义行为

Item 35: 用mismatch或lexicographical_compare实现大小写不敏感比较

Item 36: 正确实现copy_if

  • C++98无copy_if,需自定义

Item 37: 用accumulate或for_each汇总范围

  • accumulate适合数值汇总
  • for_each适合副作用操作

六、函数对象 — Item 38-42

Item 38: 设计按值传递的函数对象类

  • 函数对象通常按值传递给算法
  • 确保拷贝构造函数高效

Item 39: 谓词必须是纯函数

  • 无副作用
  • 相同输入产生相同输出

Item 40: 使函数对象类可适配

  • 继承unary_function/binary_function
  • 提供argument_type/result_type等typedef

Item 41: 理解ptr_fun/mem_fun/mem_fun_ref的用途

  • 将函数指针/成员函数转为函数对象
  • C++11后用bind/lambdas替代

Item 42: 确保less对应operator<

  • 关联容器默认使用less
  • 自定义类型需重载operator<

七、STL编程 — Item 43-50

Item 43: 优先使用算法调用而非手写循环

  • 更简洁、更少错误、可能被优化

Item 44: 优先使用成员函数而非同名算法

  • list::sort比std::sort更高效(利用splice)
  • deque无排序算法对应物

Item 45: 区分count/find/二分搜索/bound函数

  • count: 统计相等元素数
  • find: 线性查找
  • binary_search: 判断是否存在
  • lower_bound/upper_bound/equal_range: 定位范围

Item 46: 考虑用函数对象替代函数作为算法参数

  • 函数对象可携带状态
  • 可被内联优化

Item 47: 避免写出难以维护的代码

  • 过度使用模板元编程
  • 过度使用非标准扩展

Item 48: 始终包含正确的头文件

  • C++标准头文件无扩展名
  • <vector>而非<vector.h>

Item 49: 学会解读STL相关编译器诊断

  • 替换basic_string等冗长类型为简短别名
  • 替换_tree等实现细节类型

Item 50: 熟悉STL相关网站


与其他笔记的关联