1. 标准库源码分布
2. Rvalue references
右值引用,按理来说应当属于语言的部分,但是由于它接下去都会和标准库有关,所以放入第二讲: 右值引用,为了解决不必要的copy和完美转发。 当赋值的右手边是一个右值,左手边(接受段)可以去偷右手边的resource,而不需要去执行allocation. 左值:可以出现在operator=的左侧 右值:只能出现在operator=的右侧 按理而言临时对象是一个右值,不能放在operator=的左侧,但是string和complex试验却产生了意想不到的结果,编译居然能通过。上图右边也有解释,我们不去关心这件事。 最重要两件事:临时对象是一个右值、右值只能出现在operator=的右侧。 再往下看: 上图函数返回的东西是一个右值,再取地址&就报错。
我们熟知的vector的insert是copy,但是2.0有了新的。 这里的例子会调用2,因为遇到了临时对象,是一个右值,则会调用右值引用的版本。 而所谓偷(move搬移)一开始就是浅拷贝,而浅拷贝是危险的,所以拷贝完会把原来的指针打断。这才是偷的精髓,即move之后原来的就会打断,就不能再使用了。 在例子中就是,原来这个右值Vtype(buf)临时对象,在move之后自身被打断,不能再用了,这很好,因为本身就是临时对象。 还有一种情况是,insert的是一个左值而不是临时对象,但是我很清楚之后不会再用他,那么我也可以让他走move这种方式。但是明明是左值想要走右值的路怎么办呢? 标准库于是给了std::move这样一个东西,只要把左值给进去就相当于拿到了它的Rvalue reference,就会走第二条路(move的路)
我的补充:关于智能指针
比如智能指针的unique_ptr也有move语义(偷的用法):
鼓励用std::make_unique<>(): 比如下图,压根不用care前三个fun中指针的生命周期:
而对于shared_ptr: shared_ptr在多线程场景用的比较多,而unique_ptr可能更常见于一些单线程的函数。语法上和unique_ptr大同小异,唯一区别是shared_ptr可以赋值,相当于拷贝,use_count+1,而use_count一变为0就立刻销毁,区别于一些语言的垃圾回收机制。 最后是weak_ptr: 所有的weak_ptr必须来自于shared_ptr,用lock函数会返回他的shared_ptr,暂时升级成shared_ptr,use_count++
3. Perfect Forwarding
源代码: 第一个版本G2.9是C++2.0之前的,只有一个版本。4.9即有两个版本。 再回到这张图: 这里用insert之后再交给MyString的move ctor去处理,经过了交接,会导致不完美。 用std::forward就会使用完美的快递,再往下看: 从这个例子我们可以看到,这里2明明是一个右值,但是forward(2)里面再调用process函数的时候却调用了process左值的版本,这便是不完美的转交。 原因是传入i之后,是一个具名变量named object 而从forward(move(a))也发现会出错。 所以要解决,这就是不完美的转发Unperfect Forwarding 下面是标准库的做法:
4. 写一个move aware class
可以看到move语义的时候一定要把原先的指针置为NULL(打断),否则原来的临时对象析构的时候就会把指针指向的东西析构掉,就会出错。 然后在析构的时候就要加if判断,指针非空去析构。
5. move aware class对容器效能的测试
如下,vector:一个放有move版本的,一个放没有的,测试结果效率差别很大。(有时候更是巨大的多)
最后发现,带不带move对于vector影响最大,对于deque在insert在中间某个地方要推(挤)影响就会很大,要是是在结尾加就不大。 下面再来讨论下图这个是为什么: 就是下图那三行: 第一个动作,真正的copy,当然慢: 第二个动作,把整个容器当作右值,那个容器的资源resource就被”偷”了,之后往下就不能用了: 而第三个动作,是因为一个vector内部是由三根指针刻画的,swap只交换三个指针速度当然快的不得了。
6. 容器结构与分类
下图是所有标准库的容器,红色的就是2.0新增加的:
7. 容器array
array其实就是一个C++的数组,只是包装成class的样子: 上图是2.9版比较简单,之后就变得很复杂了:
8. 容器hashtable
哈希表,或叫散列表,2.0之前其实也存在了,由hashtable之后做出了set和map,之后又实现了unordered的一些容器,即他们的底层都是hashtable:
篮子是由vector实现的(上图的buckets vector),篮子的个数会成长,底下引出的则是由链表实现的。 业界的一些规范:只要底下的链表数大于篮子数,就增大篮子数(扩增两倍),然后重来。如上图的再安插48个,6+48=54 而落在哪个篮子很简单:如108 % 53 = 2,108一开始就在第二号个篮子里,即mode篮子总数。 但是一般我们不会放整数,会放如对象,那么这里的108就是那个元素的代码,叫做hashcode。比如说字符串,就会有一个函数去计算一个字符串对应的代码,代码越乱越好减少冲突。 所以这样一个算出代码的函数就是hashfunction
9. unordered
2.0之后把hash改成了unordered: 篮子bucket个数永远大于元素个数,所以放100w个元素,篮子个数一定大于100w(因为等于的时候会扩充篮子) 当然,如果是set则要看不一样的值。 其实标准库里头已经把篮子个数算好了,一个例子:
10. hash function
下图,标准库有一些hash function,可以用来针对基本类型: 比如这里hash()就做了一个临时对象,是一个hash function,然后就可以当作函数继续调用: hash()(123) 可以看到他们对应的hash code 看到G2.9版本的定义,就是模板的特化: 针对字符串,有下面这样的经验公式: 而2.9的时候对于浮点数都没有这样的hashfunction,而G4.9之后就有了 如上,有这么一些特化版本。 G4.9一路特化,调用了_Hash_bytes函数,只有声明没有定义,怀疑是编译已经放入二进制文件去了。
目前看到C++新标准-C++11/14 P31 Hash function 后续几小节找不到资源,下一步看STL
|