Boolan(博览网)——STL与泛型编程(第十周)

目录

1. 一个万用的 Hash Function

2. tuple(元组)浅谈

3. type traits 深入探索

3.1 type traits 应用
3.2 type traits 实现

4. cout 浅谈

5. moveable 深入探索

5.1 moveable 元素对于 multiset 速度效能的影响
5.2 写一个 moveable calss
5.3 moveable 测试及代码剖析

1. 一个万用的 Hash Function

我们都知道,如果要使用哈希表作为底层容器构建自己设计的上层容器,必须要设计一个该容器内元素可用的返回值足够乱的 Hash Function。之前的 STL 版本中系统自带的 Hash Function 基本什么也没做,只是忠实地返回原值。如果都要自己重新设计的话,可是说是非常烦人了。那么新版本有什么好东西提供给我们解决这个问题么?

答案是有的!这里就要介绍一个「万用」的 Hash Function,弄清楚它的运作原理,以后再设计 Hash Function 时就非常便捷了,先来看看如何使用定义好的 Hash Function:

Boolan(博览网)——STL与泛型编程(第十周)_第1张图片
  • 形式1:利用函数对象调用 Hash Function
  • 形式2:利用函数调用 Hash Function(可以看到此方法在声明时比前者麻烦太多,个人不推荐)

接下来就来介绍下这个万用的 Hash Function 是如何设计的:

Boolan(博览网)——STL与泛型编程(第十周)_第2张图片

左上角这种想当然的方式很容易发生碰撞,不是我们需要的设计方法。而所谓的万用的 Hash Function 利用 C++ 11 新引入的 variadic templates 技术,允许随意数量的任意类型的元素去调用 hash_val() 函数来产生各自合适的哈希值(利用 typename... + 递归逐层处理元素),妙哉!

相信大家都会好奇,处理方法中为什么要加上 0x9e3779b9 这么个莫名其妙的数值呢?

其实它大有来头:

Boolan(博览网)——STL与泛型编程(第十周)_第3张图片

orz 请收下我的膝盖!

除了函数对象和函数这两种形式,还可以 struct hash 偏特化的形式来实现 Hash Function:

Boolan(博览网)——STL与泛型编程(第十周)_第4张图片
Boolan(博览网)——STL与泛型编程(第十周)_第5张图片

新版本里终于提供了 hash 的默认版本!

2. tuple(元组)浅谈

所谓元组,就是不同类型的元素的集合:

Boolan(博览网)——STL与泛型编程(第十周)_第6张图片

能把不同类型的元素集合在一起,这么神奇?怎么做到的呢?

这里又用到了可变模板这个新武器!看来这个升级对于 C++ 来说是如虎添翼啊!

Boolan(博览网)——STL与泛型编程(第十周)_第7张图片

3. type traits 深入探索

traits 编程技法很棒,适度弥补了 C++ 语言本身的不足。STL 只对迭代器加以规范,制定出 iterator_traits 这样的东西。SGI 把这种技法进一步扩大到迭代器以外的世界,于是有了所谓的 __type_traits。双底线前缀词意指这是 SGI STL 内部所用的东西,不在 STL 标准规范之内。

iterator_traits 负责萃取迭代器的特性,__type_traits 则负责萃取型别(type)的特性。此处我们所关注的型别特性是指:这个型别是否具备 non-trivial default ctor(非不重要的缺省构造,也就是重要的...)?是否具备 non-trivial copy ctor?是否具备 non-trivial assignment operator?是否具备 non-trivial dtor?如果答案是否定的,我们在对这个型别进行构造、析构、拷贝、赋值等操作时,就可以采用最有效率的措施(例如根本不调用那些身居高位、不谋实事的那些 constructor,destructor),而采用内存直接处理操作如 malloc()、memcpy() 等等,获得最高效率。这对于大规模而操作频繁的容器,有着显著的效率提升。

定义于 SGI 中的 __type_traits,提供了一种机制,允许针对不同的型别属性(type attributes),在编译时期完成函数派送决定(function dispatch)。这对于撰写 template 很有帮助,例如,当我们准备对一个“元素型别未知”的数组执行 copy 操作时,如果我们能事先知道其元素型别是否有一个 trivial copy constructor,便能够帮助我们决定是否可使用快速的 memcpy()或 memmove()。

根据 iterator_traits 得来的经验,我们希望,程序之中可以这样运用
__type_traits,T 代表任意型别:


__type_traits::has_trivial_default_constructor
__type_traits::has_trivial_copy_constructor
__type_traits::has_trivial_assignment_operator
__type_traits::has_trivial_destructor
__type_traits::is_POD_type    // POD : Plain Old Data(C 里的 struct,只有 data 没有 function)

我们希望上述式子响应我们「真」或「假」(以便我们决定采取什么策略),但其结果不应该只是个 bool 值,应该是个有着真/假性质的「对象」,因为我们希望利用其响应结果来进行参数推导,而编译器只有面对 class object 形式的参数,才会做参数推导。为此,上述式子应该传回这样的东西:


struct __true_type { };
struct __false_type { };

这两个空白 classes 没有任何成员,不会带来额外负担,却又能够标识真假,满足我们所需。

为了达成上述五个式子,__type_traits 内必须定义一些 typedefs,其值不是 __true_type 就是 __false_type。下面是 SGI 的做法:

Boolan(博览网)——STL与泛型编程(第十周)_第8张图片

为什么 SGI 把所有内嵌型别都定义为 __false_type 呢?
SGI 定义出最保守的值,然后再针对每一个标量型别(scalar types)设计适当的 __type_traits 特化版本,这样就解决了问题。

Boolan(博览网)——STL与泛型编程(第十周)_第9张图片

3.1 type traits 应用

__ type_traits 在 SGI STL 中的应用很广。比如 uninitialized_fill_n() 全局函数:

该函数以 x 为蓝本,自迭代器 first 开始构造 n 个元素。为求取最大效率,首先以 value_type() 萃取出迭代器 first 的 value type,再利用 __type_traits 判断该型别是否为 POD 型别:

Boolan(博览网)——STL与泛型编程(第十周)_第10张图片

以下就「是否为 POD 型别」采取最适当的措施:

Boolan(博览网)——STL与泛型编程(第十周)_第11张图片

函数定义时,如果函数内不会用到某个参数,可以只给出类型,不给出参数名~

第二个例子是负责对象析构的 destroy() 全局函数。此函数的源代码和分析在前面的笔记中已有介绍,这里不再赘述:

Boolan(博览网)——STL与泛型编程(第十周)_第12张图片
Boolan(博览网)——STL与泛型编程(第十周)_第13张图片
Boolan(博览网)——STL与泛型编程(第十周)_第14张图片
Boolan(博览网)——STL与泛型编程(第十周)_第15张图片
Boolan(博览网)——STL与泛型编程(第十周)_第16张图片

第三个例子是 copy() 全局函数。这个函数有非常多的特化(specialization)与强化(refinement)版本,殚精竭虑,全都是为了效率考虑,希望在适当的情况下采用最「雷霆万钧」的手段。最基本的想法是这样的:

Boolan(博览网)——STL与泛型编程(第十周)_第17张图片
Boolan(博览网)——STL与泛型编程(第十周)_第18张图片
Boolan(博览网)——STL与泛型编程(第十周)_第19张图片
Boolan(博览网)——STL与泛型编程(第十周)_第20张图片
Boolan(博览网)——STL与泛型编程(第十周)_第21张图片

3.2 type traits 实现

Boolan(博览网)——STL与泛型编程(第十周)_第22张图片

先利用 remove_cv 把 _Tp 中的 const 和 volatile 拿掉之后再进入 _is_void_helper 来得到 void 的判断。

Boolan(博览网)——STL与泛型编程(第十周)_第23张图片

编译器在编译的时候当然知道我们关心的问题的答案,但是这部分代码都未公开,编译器在「暗中帮助」我们(笑

4. cout 浅谈

ostream 里有 << 的各种重载版本,这些都是可以直接接 cout 使用的:

Boolan(博览网)——STL与泛型编程(第十周)_第24张图片

5. moveable 深入探索

移动构造能避免分配和释放内容的额外开销,相应操作的性能会好很多。

5.1 moveable 元素对于 multiset 速度效能的影响

对于 vector 这种连续线性空间,移动构造要比拷贝构造快很多,而对于 list、deque、multiset、unordered_multiset等容器,两者差别不大。

5.2 写一个 moveable calss

Boolan(博览网)——STL与泛型编程(第十周)_第25张图片
Boolan(博览网)——STL与泛型编程(第十周)_第26张图片

5.3 moveable 测试及代码剖析

Boolan(博览网)——STL与泛型编程(第十周)_第27张图片
Boolan(博览网)——STL与泛型编程(第十周)_第28张图片
Boolan(博览网)——STL与泛型编程(第十周)_第29张图片

答案是肯定的

你可能感兴趣的:(Boolan(博览网)——STL与泛型编程(第十周))