本周内容
(1)一个万能的hash function
(2)Tuple用例
(3)Type traits以及traits 实现
(4)cout
(5)moveable元素对于容器速度效能的影响
一 一个万用的hash function
- 但使用以hash table为底层的容器时,遇到一个难题,必须要为放的元素写一个hash function。正如东西是由原子分子组成一样,在计算机编程里面,所有的data也是由原始的整数、浮点数、字符串组成的,基本的数值型这些本身都有hash function,他们的hash function传回来的就是自己。有没有可能把一个设计出来的数据结构,一个元素,把它拆分开来,然后把它各自的hash code(hash function产生hash code)加起来,就变成这个元素的hash code,可以不可以这样做呢,好像言之成理。所谓的hash function是将将产生的hash code做到越乱越好,不要重复。
- 下一段代码是hash function的写法形式:
#include<functional>
class Customer {
...
};
//-------------------------形式1成员函数---------------------
class CustomerHash
{
public:
std::size_t operator()(const Customer& c) const {
return...
};
//-----------------------------------------------------------
unordered_set<Customer,CustomerHash> custset;
//形式1模板参数只需要填元素类型和函数类型就可以,自动产生函数对象被调用
//-------------------------形式2一般函数---------------------
size_t customer_hash_func(const Customer& c){
return...
};
//-----------------------------------------------------------
unordered_set<Customer,size_t(*)(const Customer&)>
custset(20,customer_hash_func);
//形式2模板参数需要填元素类型和函数类型,创建容器时候还要把真正的函数地址放进来
- 将每个hash code相加太过天真:元素容易碰撞,每个篮子挂的元素多,查找慢。从TR1开始有了下面1234帮你写hash function。typename...表示可以接受任意多的模板参数。
- 借由variadie templates这种语法,可以放任意元素个数,任意类型的参数。
-
9e3779b9是黄金比例的应用。
-
还有第三种形式实现,那就是通过对自己的元素作一个偏特化版本实现hash function。G4.9以后有了string的偏特化版本。
二 tuple用例
- C++11中的tuple(元组)是一个固定大小的不同类型值的集合,是泛化的std::pair。我们可以把他当做一个通用的结构体来用,不需要创建结构体又获取结构体的特征,在某些情况下可以取代结构体使程序更简洁,直观。tuple可以指定任意类型元素。
-
通过继承自己(尾部的部分)实现递归。
GNC4.8 tuple实作如下:
tuple用例如下:
三 Type traits以及traits 实现
- G2.9的type traits写的非常简单,泛化版本(默认)六个typedef,拷贝构造、析构函数等都是重要的,意味着如果要拷贝一百万个数据,要调用一百万个拷贝构造函数、析构函数。如果你写出一个类型,知道那个类型不重要,那就可以为此写一个特化版本,说这些函数都不重要。
-
设计一个复数,有实部和虚部,不必为他写析构函数、拷贝构造函数、拷贝赋值函数,因为不写的话编译器有一个默认版本,那个版本所作的功能已经够了,复数的拷贝是一个位一个位拷贝,不需要特别做事情,不需要写析构函数,因为他马上就“死掉了”。复数没有指针,这些都不重要。
以下是type traits测试:
- 一个类带有指针一定要写析构函数,不带指针多半不用写。
-
以下是测试结果,将string丢到里面,右边是一些类型的反馈,1代表是,0代表不是。
- 多态(polymorphic)类是一种有声明或者继承了虚函数的类。
- 新版本不用像旧版本一样还要写偏特化版本来配合它。
那么type traits是怎么实现的呢,以is_void为例,它先要将const和volatile(多线程用到,易挥发)拿掉,用remove_cv函数实现,remove_const和remove_volatile各用一个泛化和偏特化版本的函数来使得传入的是否有const(volatile)都会去掉这两个。然后再进入_is_void_helper,如果是void那么传回真,如果不是则传回假,也是用泛化和偏特化。
四 cout
cout是一个类的对象,extern表示cout可以被外界看到,它能接受这么多类型是因为它作了大量的<<重载。如果你想写自己的类型,那么就要仿照写出<<的重载。
五 moveable元素对于容器速度效能的影响
buf分别用moveable和non-moveable以insert的方式放进来,红黑树和哈希表的容器,你只要insert,那么它就会落在该落的地方,但是vector、list、deque你要insert必须要告诉它哪里insert。stl里的insert提供插入位置选择,但对于关联式容器,就算指定插入位置,如果不合理,他还是落在它应该落在的地方。
- vector放三百万个元素,而拷贝构造函数却调用了七百多万次,是因为vector的成长是两倍两倍的,在成长的过程中引发拷贝构造。如果一开始指定有足够大的vector,就不会有这么大的拷贝构造。
- moveable和non-moveable的效率差别很大
- move copy和copy效率差别很大
- list、deque、关联式容器放三百万个元素,拷贝构造也调用三百万次,这是因为他们不像vector是连续的内存空间。
- moveable和non-moveable的效率差别不大
- move copy和copy效率差别不大
- 虽然list、deque、关联式容器一开始放元素moveable和non-moveable的效率差别不大,但是后来的操作也会影响。
下面是moveable class源代码,move其实是一种浅拷贝,不过他多了一个将原来的指针打断的操作。
用move版本的条件是:move以后原来的东西不再使用。下图代码中V1type(buf)是一个临时对象,放进去以后就消失了,所以会调用move版本,而c1不是临时对象,c11(c1)时不会调用move版本。如果有信心可以调用move版本,那就可以像下面代码一样加上std::move,但是必须确保接下来不会再用到c1。
- std::string有moveable版本的拷贝构造和拷贝赋值。