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

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

作者: Michael_SR | 来源:发表于2017-12-24 22:30 被阅读0次

    目录

    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:

    • 形式1:利用函数对象调用 Hash Function
    • 形式2:利用函数调用 Hash Function(可以看到此方法在声明时比前者麻烦太多,个人不推荐)

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

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

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

    其实它大有来头:

    orz 请收下我的膝盖!

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

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

    2. tuple(元组)浅谈

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

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

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

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

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

    
    __type_traits<T>::has_trivial_default_constructor
    __type_traits<T>::has_trivial_copy_constructor
    __type_traits<T>::has_trivial_assignment_operator
    __type_traits<T>::has_trivial_destructor
    __type_traits<T>::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 的做法:

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

    3.1 type traits 应用

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

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

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

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

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

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

    3.2 type traits 实现

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

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

    4. cout 浅谈

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

    5. moveable 深入探索

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

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

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

    5.2 写一个 moveable calss

    5.3 moveable 测试及代码剖析

    答案是肯定的

    相关文章

      网友评论

          本文标题:Boolan(博览网)——STL与泛型编程(第十周)

          本文链接:https://www.haomeiwen.com/subject/clhkgxtx.html