> For the complete documentation index, see [llms.txt](https://gists.lanlance.cn/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://gists.lanlance.cn/cs/cpp.md).

# C++

## 1. 预处理阶段能做什么

* 预处理不属于 C++ 语言，过多的预处理语句会扰乱正常的代码，除非必要，应当少用慎用；
* “#include”可以包含任意文件，所以可以写一些小的代码片段，再引进程序里；
* 头文件应该加上“Include Guard”，防止重复包含；
* “#define”用于宏定义，非常灵活，但滥用文本替换可能会降低代码的可读性；
* “条件编译”其实就是预处理编程里的分支语句，可以改变源码的形态，针对系统生成最合适的代码。

## 2. 编译阶段能做什么

* “属性”相当于编译阶段的“标签”，用来标记变量、函数或者类，让编译器发出或者不发出警告，还能够手工指定代码的优化方式。
* 官方属性很少，常用的只有“deprecated”。我们也可以使用非官方的属性，需要加上名字空间限定。
* static\_assert 是“静态断言”，在编译阶段计算常数和类型，如果断言失败就会导致编译错误。它也是迈向模板元编程的第一步。
* 和运行阶段的“动态断言”一样，static\_assert 可以在编译阶段定义各种前置条件，充分利用 C++ 静态类型语言的优势，让编译器执行各种检查，避免把隐患带到运行阶段。

## 3. 面向对象编程

* “面向对象编程”是一种设计思想，要点是“抽象”和“封装”，“继承”“多态”是衍生出的特性，不完全符合现实世界。
* 在 C++ 里应当少用继承和虚函数，降低对象的成本，绕过那些难懂易错的陷阱。
* 使用特殊标识符“final”可以禁止类被继承，简化类的层次关系。
* 类有六大基本函数，对于重要的构造 / 析构函数，可以使用“= default”来显式要求编译器使用默认实现。
* “委托构造”和“成员变量初始化”特性可以让创建对象的工作更加轻松。
* 使用 using 或 typedef 可以为类型起别名，既能够简化代码，还能够适应将来的变化。

## 4. 自动类型推导

* “自动类型推导”是给编译器下的指令，让编译器去计算表达式的类型，然后返回给程序员。
* auto 用于初始化时的类型推导，总是“值类型”，也可以加上修饰符产生新类型。它的规则比较好理解，用法也简单，应该积极使用。
* decltype 使用类似函数调用的形式计算表达式的类型，能够用在任意场合，因为它就是 一个编译阶段的类型。
* decltype 能够推导出表达式的精确类型，但写起来比较麻烦，在初始化时可以采用 decltype(auto) 的简化形式。
* 因为 auto 和 decltype 不是“硬编码”的类型，所以用好它们可以让代码更清晰，减少后期维护的成本。

## 5. const & volatile & mutable

1. const

* 它是一个类型修饰符，可以给任何对象附加上“只读”属性，保证安全；
* 它可以修饰引用和指针，“const &”可以引用任何类型，是函数入口参数的最佳类型；
* 它还可以修饰成员函数，表示函数是“只读”的，const 对象只能调用 const 成员函数。

2. volatile

* 它表示变量可能会被“不被察觉”地修改，禁止编译器优化，影响性能，应当少用。

3. mutable

* 它用来修饰成员变量，允许 const 成员函数修改，mutable 变量的变化不影响对象的常量性，但要小心不要误用损坏对象。

尽可能多用 const，让代码更安全

## 6. 智能指针到底智能在哪里

* 智能指针是代理模式的具体应用，它使用 RAII 技术代理了裸指针，能够自动释放内存，无需程序员干预，所以被称为“智能指针”。
* 如果指针是“独占”使用，就应该选择 unique\_ptr，它为裸指针添加了很多限制，更加安全。
* 如果指针是“共享”使用，就应该选择 shared\_ptr，它的功能非常完善，用法几乎与原始指针一样。
* 应当使用工厂函数 make\_unique()、make\_shared() 来创建智能指针，强制初始化，而且还能使用 auto 来简化声明。
* shared\_ptr 有少量的管理成本，也会引发一些难以排查的错误，所以不要过度使用。

## 7. C++ 中的异常处理

* 异常是针对错误码的缺陷而设计的，它不能被忽略，而且可以“穿透”调用栈，逐层传播到其他地方去处理；
* 使用 try-catch 机制处理异常，能够分离正常流程与错误处理流程，让代码更清晰；
* throw 可以抛出任何类型作为异常，但最好使用标准库里定义的 exception 类；
* 完全用或不用异常处理错误都不可取，而是应该合理分析，适度使用，降低异常的成本；
* 关键字 noexcept 标记函数不抛出异常，可以让编译器做更好的优化。

## 8. lambda 表达式

* lambda 表达式是一个闭包，能够像函数一样被调用，像变量一样被传递；
* 可以使用 auto 自动推导类型存储 lambda 表达式，但 C++ 鼓励尽量就地匿名使用，缩小作用域；
* lambda 表达式使用“\[=]”的方式按值捕获，使用“\[&]”的方式按引用捕获，空的“\[]”则是无捕获（也就相当于普通函数）；
* 捕获引用时必须要注意外部变量的生命周期，防止变量失效；
* C++14 里可以使用泛型的 lambda 表达式，相当于简化的模板函数。

## 9. C++ 中的字符串

* C++ 支持多种字符类型，常用的 string 其实是模板类 basic\_string 的特化形式；
* 目前 C++ 对 Unicode 的支持还不太完善，建议尽量避开国际化和编码转化，不要“自讨苦吃”；
* 应当把 string 视为一个完整的字符串来操作，不要把它当成容器来使用；
* 字面量后缀“s”表示字符串类，可以用来自动推导出 string 类型；
* 原始字符串不会转义，是字符串的原始形态，适合在代码里写复杂的文本；

## 10. 详解 C++ 容器

* 标准容器可以分为三大类，即顺序容器、有序容器和无序容器；
* 所有容器中最优先选择的应该是 array 和 vector，它们的速度最快，开销最低；
* list 是链表结构，插入删除的效率高，但查找效率低；
* 有序容器是红黑树结构，对 key 自动排序，查找效率高，但有插入成本；
* 无序容器是散列表结构，由 hash 值计算存储位置，查找和插入的成本都很低；
* 有序容器和无序容器都属于关联容器，元素有 key 的概念，操作元素实际上是在操作 key，所以要定义对 key 的比较函数或者散列函数。

## 11. 多线程编程

* 多线程是并发最常用的实现方式，好处是任务并行、避免阻塞，坏处是开发难度高，有数据竞争、死锁等很多“坑”；
* call\_once() 实现了仅调用一次的功能，避免多线程初始化时的冲突；
* thread\_local 实现了线程局部存储，让每个线程都独立访问数据，互不干扰；
* atomic 实现了原子化变量，可以用作线程安全的计数器，也可以实现无锁数据结构；
* async() 启动一个异步任务，相当于开了一个线程，但内部通常会有优化，比直接使用线程更好。
