智能指针
- 智能指针是代理模式的具体应用,它使用RAII技术代理了裸指针,能够自动释放资源,无须程序员干预,所以被称为智能指针。
- 如果指针是独占使用,就应该选择unique_ptr,它为裸指针添加了很多限制,更加安全。
- 如果指针是共享使用,就应该选择shared_ptr,它的功能非常完善,用法几乎与原始指针一样。
- 如果可能存在循环引用,或者不需要shared_ptr那样的强共享关系,就应该选择weak_ptr,它不影响引用计数,也可以随时转换成shared_ptr.
- 应当调用工厂函数make_unique()/make_shared()来创建智能指针,强制初始化,还能用auto来简化声明。
- shared_ptr有较低的管理成本,有时也会引发一些难以排查的错误,所以不要过度使用。
- 既然我们理解了智能指针,就尽量不要再使用裸指针/new/delete来操作内存了。
字符串
- c++支持多种字符串类型,常用的string其实是模板类basic_string的特化形式。
- 目前c++对Unicode的支持还不太完善,建议尽量避开国际化和编码转换。
- 应当把string视为一个完整的字符串来操作,不要把它当成容器来使用。
- 字面量后缀“S”表示字符串类,可以用来自动推导string类型。
- 原始字符串不会转义,是字符串的原始形态,适合在代码里写复杂的文本。
- string_view是轻量级的字符串视图,成本很低,非常适合只读的场合,但要担心引用失效。
- C++20里新增的格式化功能非常易用,比传统的printf()/iostream都要好。
- 处理文本应当使用正则表达式库regex,它支持匹配,查找,替换,功能非常强大。
标准容器
- 标准容器可以分为三大类:顺序容器,有序容器和无须容器。
- 所有容器中最应该优先选择的是array/vector,它们的速度最快。成本最低。
- list/forward_list是链表结构,插入和删除的效率高,但查找效率低。
- 有序容器是红黑树结构,对key自动排序,查找效率高,但有插入成本。
- 无序容器是散列表结构,由散列值计算存储位置,查找和插入的成本都很低。
- 有序容器和无序容器都属于关联容器,元素有key的概念,操作元素实际上是在操作key,所以要定义对key的相等比较函数或者散列函数。
- 多利用类型别名,不要“写死”容器定义。因为容器的大部分接口是相同的,所以只要变动别名定义,就能够随意改换不同的容器,对于开发、测试都非常方便。
特殊容器
- optional主要用来表示值有效或者无效,用法很像智能指针
- variant是一种异质容器,可以在运行时改变类型,实现泛型多态。
- any与variant类似,但可以容纳任意类型,在运行时检查类型会更灵活。
- tuple可以打包多种不同类型的数据,也可以算是一种异质容器。
- optional/variant/any在任何时刻只能持有一个元素,而tuple则可以持有多个元素。
标准算法
- 算法是专门用来操作容器的函数,是一种智能for循环,他的最佳搭档是lambda表达式。
- 算法通过迭代器来间接操作容器,使用两个端点指定操作范围,迭代器决定了算法的能力。
- for_each算法是for的替代品,以函数式编程替代了面向过程编程,更具有可读性。
- 有多种排序算法,基本的是sort,但应该根据实际情况选择stable_sort/parial_sort等更合适的算法,避免浪费。
- 在有序容器上可以执行二分查找,应该使用的算法不是binary_search,而应该是lower_bound。
- list/set/map提供等价的排序,查找函数,更适合用于自己的数据结构。
- find/search系列是通用的查找算法,效率不高,但不必排序也能使用。
- C++20增加了range/view的概念,可以让算法有更强的表达能力,用起来更简单、轻松。
线程并发
- 多线程是并发常用的实现方式,好处是任务并行,可以避免阻塞,坏处是开发难度高,有数据竞争,死锁等很多坑。
- call_once实现了单次调用的功能,可以避免多线程初始化的冲突。
- thread_local实现了线程局部存储,可以让每个线程都独立访问数据,互不干扰。
- atomic实现了原子化变量,可以用作线程安全的计数器,也可以实现无锁数据结构。
- async启动了一个异步任务,相当于开启一个线程,但内部通常会优化,比直接使用线程thread/jthread更好。