======STL与泛型编程 第五周======
本页内容是作为 //Boolan// C++ 开发工程师培训系列的笔记。\\
sizt_t customer_hash_func(const Customer& c) {
....
}
而使用时候需要指定**元素类型**和**哈希函数类型**:
unordered_set
如果是成员哈希函数,需要以运算符重载的形式实现函数功能:
std::size_t operator() (const Customer& c) const {...};
调用的时候在实例化类的时候指定该哈希参数即可:
unordered_set
偏特化版本是成员函数的另外一种形式,是使用结构体实现的:
template
使用的方法与类成员函数的使用方法一致,也是通过 ''()'' 的重载来实现。不过不同的是,使用的时候需要借助 ''hash'' 结构体指定类型:
template<>
struct hash
===万用哈希函数的实现===
哈希函数需要针对元素的内容来进行 //Hash code// 的计算。因此,我们需要哈希函数有能力接收**不同类型和不同数量**的参数。//TR1// 版本的 C++ 中提供了一种解决方法:通过**递归**的手段来对每一个元素进行 //Hash code// (Seed) 的计算,然后把这些 //Hash code// 通过一定的算法组合在一起。
\\
\\
为了实现这个方法,我们需要按递归的原理确定几个 case:
* 基础情况:单个 //Seed// 与单个元素的基础部分。该情况应该直接使用指定的算法算出新的 //Seed//,也就是只计算一次(//3//)。
* 多个参数情况:该情况需要先将参数列表进行分割:
* 如果未指定 //Seed// 的默认值,则需要在函数里设定 //Seed// 的默认值(//1//)。
* 如果指定了 //Seed// 的默认值,那么直接进行元素的分割操作(//2//)。
根据以上的思路,我们不难得出相应的实现方法:
\\
\\
基础形式(//3//):
template
多个参数形式,无 //Seed// 初始值(//1//):
template
多个参数形式,有 //Seed// 初始值(//2//):
template
\\
基础的 ''hash_combine()'' 函数则迭代的对传递进来的每一个 //Seed// 通过如下算法组合到了最后的结果中:
inline void hash_combine(size_t& sed, const T& val) {
seed ^=std::hash
值得注意的是,以上所有的函数在传递 //Seed// 的时候都是通过引用来传递的。//Seed// 在 (//1//)中初始化,直到递归完毕得到结果。使用**引用传递**保证了在 //Seed// 的传递过程中实现正确的迭代累积。
\\
\\
//PS: ''0X9e3779b9'' 是黄金分割的小树部分 ^_^。//
====Tuple的用例====
//Tuple// 是除了 //STL// 内容之外,标准库定义的一种数据结构。这种数据结构可以指定存放**任意个元素**,每个元素的**类型**也可以是**任意的**。//Tuple// 的使用方法如下:
//initialize
tuple
===Tuple的实现细节===
//Tuple// 是一个类,本身也是通过 C++ 中模板的无限参数的功能来实现的。//Tuple// 在实现中分为**头部**(//Head//)和**尾部**(//Tail//)。头部代表了当前 //Tuple// 中第一个元素,而尾部则代表了除此之外剩下的元素。这个看上去和先前的万用哈希函数非常相似;但有一点明显不同的是,//Tuple// 的实现是通过**递归加上继承**的方法来实现的。
\\
\\
template<> class tuple<> {}; // basic case
class tuple: private tuple {
....
}
在类中,每一个类都存储当前的头部元素:
Head m_head;
tpyename Head::type head() { return m_head; } // member function that return current head
同时,当前的类有一个指针,指向当前类中尾部的开头:
inherited& tail() { return *this; }
看到这里你可能会问:''tail()'' 是怎么知道我们指向的是当前的尾部开头,而不是当前的类开头的?
\\
\\
实际上, ''tail()'' 在这里就是指向了当前的类开头,因为返回的是 ''this''。但是,我们看到前面返回的类型是 ''inherited&''; 这个类型是一个指向当前尾部的引用。因此,即便我们的指针指向的同一个位置,但是在下次递归展开以后,该指针依然能够正确表达下一次递归(子类)中的尾部开头地址。换句话说,**指针指向的地址没有变化,变化的是指针指向的数据结构的范围**。
\\
\\
type_traits_output(const T& x) {
cout << is_void::value << endl;
.....
}
C++ 11中的 //Type Traits// 可以对各种类型做出判断,详情可以见侯老师的几个例子,也可以查询上面的文档;本处就不再赘述。
===部分Type Traits的实现===
//Type Traits// 实现的本质是模板对类型的操作。对于一些复杂的判断,比如对类、结构体、C中的结构(POD)或者是 //Moveable// 元素的判断,在标准库的源码中并没有给出源码。不过可以推测的是,编译器应该参与了这一部分工作。而对于一些比较简单的类型判断,比如 ''void'' 和 ''integral'',在源码中还是能找到实现的代码的。下面就来看看这两个 //Type Traits// 是如何实现的。
\\
\\
首先,无论是 ''void'' 和 ''intergral'',在参数传入的时候,都需要考虑去掉一些会影响判断的额外状态;比如 ''const'',''volatile''。为此,这两个 //Type Traits// 通过对一个名为
''remove_cv'' 的类偏特化,对特定的,带有 ''const'' 和 ''volatile'' 关键字的类型进行了处理。''remove_cv'' 自身并不对接收的类型进行处理,而是通过在其内部的两个类 ''remove_const'' 和 ''remove_volatile'' 进行处理。这两个函数都拥有两个版本:
* 泛化版本:接收一个参数,返回参数的同类型,也就是不对参数的类型做任何处理
* 特化版本:接收一个带 ''const'' or ''volatile'' 关键字的参数,返回参数的普通类型,也就是去掉了关键字的影响。
经过以上的处理,所有带有额外关键字的类型,都会被转化为其对应的普通类型。
\\
\\
接下来,得到了存粹的类型以后,这两个 //Type Traits// 会通过自身的一个 ''helper'' 模板类,对传入的类型进行类型种类的判断。''is_void'' 的判断比较简单,只需要通过一个 ''void'' 的特化类进行判断即可;只要是类型转入了该特特化类,返回的值都会是真的。''is_integral'' 的判断也是通过对对应类型的特化返回真值来实现的。可以返回真值的类型有 ''int''、''char''、''bool''、''long''、''long long'',以及他们的 ''unsigned'' 版本。
====Cout====
//Cout// 是一个类对象。它属于 ''IO_stream_withassign'' 类,而这个类继承自 ''IOstream'' 类。这些类的对象的主要功能为输入输出。
\\
\\
//Cout// 所属的类中定义了绝大部分对基本类型的输出。但如果要使用 //Cout// 对复杂的类型进行输出的时候,需要在该类型所属类中进行相应的运算符重载。标准库中的重载接收两个参数:
* 第一参数:运算符左边的类型,比如 //Cout// 对应的就是 ''basic_ostream''
* 第二参数:运算符右边的类型,比如 //String// 对应的就是 ''basic_stream''
表现为代码如下:
operator<<(basic_ostream<_CharT,_Traits>&_os, const basic_string<_CharT,_Traits,Alloc>&_str) {...}
该形式还可以应用到对其他类型的重载,比如智能指针,线程,正则表达式等等。
====Move拷贝构造====
''move()'' 是 C++2.0 中的一种新的拷贝构造方式,其实质是拷贝构造中的**浅拷贝**。因此,在进行拷贝构造的时候,相对于传统的拷贝构造(深拷贝),''move()'' 拥有着绝对的速度优势。但也因为 ''move()'' 基于浅拷贝实现,因此使用的时候必须要注意:
* 经过 ''move()'' 拷贝构造之后,源数据因为指针的失效,已经不能够再使用了。因此,使用 ''move()'' 必须要在**不再使用源数据的情况之下操作**。
* ''move()'' 的实现往往搭配着一个**引用的引用** ''&&'' 类型,比如 ''myString(myString&& str)''。
===Move拷贝构造对容器的影响===
如果元素是支持 ''move()'' 的,我们则称该元素为 //Movable//。对于不同的容器,元素是否支持 ''move()'',对初始化是有很大的影响的。
\\
\\
除此之外,容器自身的结构也对拷贝构造有一些影响。某些容器需要在空间不足的情况下扩容,从而进行大量的拷贝构造(比如 //Vector//);而关联容器或者 //List// 则只需要在创建新元素的时候做拷贝构造。这两者之间的的差别也是非常大的。
\\
\\
\\