C++ 2.0的内容分为2部分讲解:
一, 新的语法;
二, 标准库中新的内容;
一、 新的语法
1. 声明一个类时,C++会默认生成big five成员函数。
默认构造函数,默认析构函数,默认拷贝构造,默认赋值构造,默认移动构造(移动拷贝构造,移动赋值构造)。
默认构造函数的作用:调用当前类的父类构造函数。
默认拷贝构造函数的作用:将非静态的成员变量逐个进行复制。若成员变量中存在指针,执行的是浅拷贝。因此,定义一个新类时,若成员变量中村子指针,则需要重新定义big three函数(默认拷贝构造,默认赋值构造,默认析构),将浅拷贝改为深拷贝。
2. 一般情况下,构造函数都是public权限。但是,若构造函数声明为private权限,则只有编译器有权限调用该构造函数,任何用户都无权限调用。
3. 关键字 decltype
作用:let the compiler find out the type of an expression.
应用:a) 声明返回类型;b) 元编程; c) 获取lambda函数的类型;
a)利用decltype提供一种返回类型的声明方法。
template <typename T1, typename T2>
auto add(T1 x, T2 y) -> decltype(x+y);
4. lambdas 函数
lambdas函数可以视为函数对象(function object),其对应关系可以用如下两个例子展示。
例子1
例子2
5. 可变模板
变化的是模板参数。参数的数量和类型都会变化。
利用参数个数逐一递减的特性,实现递归函数调用。
可变模板例子 1)
函数1是递归终止条件;
函数2和函数3可以并存,但是编译器会优先使用更为特化的函数2;
可变模板例子 3)
利用可变模板实现取最大值的功能。
右下角的代码,结构体 _Iter_less_iter,成员函数重载()。_Iter_less_iter(),是调用了默认构造函数?还是调用了重载函数()?
侯捷介绍:_Iter_less_iter 重载了(),因此对象可以像函数一样被调用。
可变模板例子 6)
tuple的构造函数利用可变模板实现递归继承,从而完成tuple对象的初始化。
构造函数中,在初始化列表中,调用父类构造函数 inherited(vtail...)。
使用private继承,其目的在于仅仅使用父类对象的内存空间。
存疑地方,typename的使用 typename Head::type head() { return m_head; }
可变模板例子 7)
利用可变模板实现tuple类型的对象初始化。递归复合的内存示意图如右上角所示,x<T1, T2, T3>中包含x<T2, T3> ,,,
二、 标准库新的内容
本节的教学目标:自己设计一个 move aware class,可以执行move操作。
右值引用 rvalue references
作用:右值引用可以优化一些不必要的拷贝操作,极大提升代码的效率。
1) 临时对象就是一种右值;
2) 右值只能出现在等号右边;
编译器可以识别函数名为函数所在的内存地址起点,即函数所在的地址。
注意:如下代码段中,foo为函数名;foo() 代表函数返回的东西,是一个右值,右值不可寻址。
如下例子,通过右值引用避免拷贝操作。
需要满足如下条件:
1)调用端告诉编译器,这是一个右值。可以是临时对象 或者 使用std::move()将左值转换为右值;
2)被调用端写出一个专门处理rvalue的移动赋值函数,例如 c.insert(..., &&x)。形参中的右值引用要求元素类型必须要有对应移动构造函数(与之相对应,拷贝构造函数都是深拷贝操作)。
上图代码中, c,c1,c2都是 std::vector<MyString> 类型。
其中 M c1(c); 执行深拷贝; M c2(std::move(c1)); 执行vector中3个指针的交换(swap)。
3个指针包括,vector的内存首指针,内存尾指针和数据尾指针。
被调用端
如图所示,G4.9 是 C++ 2.0。其中,vector.insert()添加了专门处理右值引用的版本(move aware)。
C++ 2.0 中新版本的 vector.insert(..., && x) 要求vector中存储的元素要有move aware的构造函数,包括移动构造和移动赋值。这样的元素类型才能进行移动操作,节省拷贝操作。
完美传递 和 不完美传递
如下 forward(2); 和 forward(move(a)); 所示,输入右值参数,在forward() 函数内部调用proces() 函数时,却变成了左值。这是不完美的传递。
C++2.0 提供完美传递 std::forward<>(),可以保持参数的特性,包括modifiable,const,lvalue 和 rvalue。
设计一个move aware class, MyString。
MyString 的成员中有指针。针对指针变量,移动构造相比拷贝构造节省了拷贝构造操作。
拷贝构造函数执行深拷贝;移动构造函数执行浅拷贝,同时把临时对象的指针置为nullptr。
在析构函数中,判断临时对象的指针 _data 是否为空?若不为空,则进行delete操作。
标准库的数据结构
红色方框标识出C++2.0新的数据结构
hashtable
篮子的大小 A=53,是一个质数。一个元素,对应一个hash code,它在hash表中存放位置 p 的计算方法为:
hash code % A = p
举个例子,55 % 53 = 2.
当元素数量大于篮子数量时,扩充篮子数量至 2A 左右(取2A附近的最近质数),然后重新计算每个元素在hashtable中的存储位置。
在实际情况中,hashtable中存放的是对象。因此,要利用 hash function 计算每个对象相应的 hash code。
如下所示,计算一些基本数据类型的 hash code。hash code的特点要够杂够乱够随机,可以避免在 hashtable 中存放的冲突。
对于 hash<int>()(123) ,第一个小括号表示创建一个临时对象,是一个 function object。第二个小括号表示调用这个function object,其行为类似于调用函数,传入参数123.
上述代码的底层实现原理如下,先创建一个泛化的 hash 结构体,再针对每一种基本数据类型进行特化。
利用hash function计算字符串的hash code