Table of Contents:

 开篇词 | C++这么难,为什么我们还要用C++?

C++ 的国际标准有 6 个版本,98, 03, 11, 14, 17,20 。
C++03是在C++98上面进行了小幅度的修订,C++11则是一次全面的大进化。
C++11—之后,C++ 以每三年一版的频度发布着新的语言标准。
C++ 是一门多范式的通用编程语言,这些不同的范式,都可以在同一项目中组合使用,这就大大增加了开发的灵活性。因此,C++ 适用的领域非常广泛,小到嵌入式,大到分布式服务器,到处可以见到 C++ 的身影。

C++ 的核心竞争力:
 抽象能力:意味着较高的开发效率,同时,更重要的是,不会因抽象而降低性能。
 性能:这不用多说了,就是快并且占用资源少。
 功耗:这是近年来我们越来越关注的问题,跟性能直接相关,性能好了功耗自然就低。移动设备上。
 和 C 的兼容性,也是 C++ 的一大优势
目前,跟 C++ 定位差不多、能有直接竞争关系的,也就是既支持高度抽象、又追求高性能
的通用编程语言,其实只有 Rust 一种。
我的个人经验,完成同样的功能,C++ 需要的代码行数一般是 Python 的三倍左右,而性能则可以达到
Python 的十倍以上。
当你的软件属于运算密集或者内存密集型,你需要性能、且愿意为性能付出额外代
价的时候,应该考虑用 C++,特别在你的代码需要部署在多台服务器或者移动设备的场
合。反之,如果性能不会成为你开发的软件的瓶颈,那 C++ 可能就不是一个最合适的工
具。

 课前必读 | 有关术语发音及环境要求

对于 CentOS 7,系统安装的 GCC 版本是 4.8,太老,你可以通过安装 centos-
release-scl 和 devtoolset-7-gcc-c++ 两个包来获得 GCC 7;随后,可以使用命令 scl
enable devtoolset-7 bash 或 . /opt/rh/devtoolset-7/enable 来启用 GCC7

即使不用较新的 C++ 特性,你也一定要用比较新的编译器。单单是输出错误信息的
友好程度,老版本和新版本就是没法比的。

 01 | 堆、栈、RAII:C++里该如何管理资源?

RAII,完整的英文是 Resource Acquisition Is Initialization,是 C++ 所特有的资源管理
方式。有少量其他语言,如 D、Ada 和 Rust 也采纳了 RAII。
RAII 依托栈和析构函数,来对所有的资源——包括堆内存在内——进行管理。对 RAII 的
使用,使得 C++ 不需要类似于 Java 那样的垃圾收集方法,也能有效地对内存进行管理。
RAII 的存在,也是垃圾收集虽然理论上可以在 C++ 使用,但从来没有真正流行过的主要原因。

 堆

序通常需要牵涉到三个可能的内存管理器的操作:
1. 让内存管理器分配一个某个大小的内存块 
2. 让内存管理器释放一个之前分配的内存块 
3. 让内存管理器进行垃圾收集操作,寻找不再使用的内存块并予以释放 
C++ 通常会做上面的操作 1 和 2。Java 会做上面的操作 1 和 3。而 Python 会做上面的操
作 1、2、3。这是语言的特性和实现方式决定的。
例子:

void foo()
{
bar* ptr = new bar();
…
delete ptr;
}

存在两个问题:
1.省略的代码部分也许会抛出异常,导致最后的 delete ptr 得不到执行。
2.更重要的,这个代码不符合 C++ 的惯用法。在 C++ 里,这种情况下有 99% 的可能性
不应该使用堆内存分配,而应使用栈内存分配。这样写代码的,估计可能是从 Java 转过
来的(偷笑)——但我真见过这样的代码

 栈

栈上的分配极为简单,移动一下栈指针而已。
栈上的释放也极为简单,函数执行结束时移动一下栈指针即可。
由于后进先出的执行过程,不可能出现内存碎片。

编译器会自动调用析构函数,包括在函数执行发生异常的情况。在发生
异常时对析构函数的调用,还有一个专门的术语,叫栈展开(stack unwinding)。

 RAII

C++ 支持将对象存储在栈上面。但是,在很多情况下,对象不能,或不应该,存储在栈上。比如:
对象很大;
对象的大小在编译时不能确定;
对象是函数的返回值,但由于特殊的原因,不应使用对象的值返回。

 02 | 自己动手,实现C++的智能指针

智能指针本质上并不神秘,其实就是 RAII 资源管理功能的自然展现而已。

class shape_wrapper {
public:
explicit shape_wrapper(
shape* ptr = nullptr)
: ptr_(ptr) {}
~shape_wrapper()
{
delete ptr_;
}
shape* get() const { return ptr_; }
private:
    shape* ptr_;
};

这个类可以完成智能指针的最基本的功能:对超出作用域的对象进行释放。但它缺了点东西:
1.只适用于 shape 类 
2. 该类对象的行为不够像指针 
3. 拷贝该类对象会引发程序行为异常 

上面给出的语义本质上就是 C++98 的 auto_ptr 的定义。如果你觉得这个实现很别扭的 
话,也恭喜你,因为 C++ 委员会也是这么觉得的:auto_ptr 在 C++17 时已经被正式从 
C++ 标准里删除了。

 03 | 右值和移动究竟解决了什么问题?

class A {
B b_;
C c_;
};

从实际内存布局的角度,很多语言——如 Java 和 Python——会在 A 对象里放 B 和 C 的
指针(虽然这些语言里本身没有指针的概念)。而 C++ 则会直接把 B 和 C 对象放在 A 的
内存空间里。这种行为既是优点也是缺点。说它是优点,是因为它保证了内存访问的局域
性,而局域性在现代处理器架构上是绝对具有性能优势的。说它是缺点,是因为复制对象的
开销大大增加:在 Java 类语言里复制的是指针,在 C++ 里是完整的对象。这就是为什么
C++ 需要移动语义这一优化,而 Java 类语言里则根本不需要这个概念。

所有的现代 C++ 的标准容器都针对移动进行了优化。

 04 | 容器汇编 I:比较简单的若干容器

 06 | 异常:用还是不用,这是个问题