最近开始看《Effective C++》,为了方便以后回顾,特意做了笔记。若本人对书中的知识点理解有误的话,望请指正!!!
STL主要由“用以表现容器、迭代器和算法”的 模板 构成,也包括若干工具性质的 模板 ,其中有一个 advance 用来将迭代器移动某个给定距离:
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d); //d大于零,迭代器向前移动d单位,小于零则向后移动
一般来说,只是 iterate+=d 的动作,但并不是所有迭代器都是这么实现的,只有支持随机访问的迭代器才支持 +=
操作。其他类型没这么大威力的迭代器,只有反复 ++
或 --
,共 d 次。
迭代器的类型有5种(C++17前,C++17起有6种,剩下的一种是 ContiguousIterator(连续迭代器))。
迭代器类别 | 说明 |
---|---|
输入迭代器 | 只能向前移动,一次一步,只可从容器中读取(不能写入)元素,且只能读取一次。该分类下只有 istream_iterator和istreambuf_iterator |
输出迭代器 | 只能向前移动,一次一步,只可向容器写入(不能读取)元素,且只能写入一次。该分类下有 ostream_iterator和ostreambuf_iterator |
前向迭代器 | 组合输入迭代器和输出迭代器的功能,而且可以读写元素一次以上 |
双向迭代器 | 组合前向迭代器和逆向迭代器的功能,该分类下有 list 、set、multiset、map和multimap的迭代器 |
随机访问迭代器 | 组合双向迭代器的功能与可以执行“迭代器算术运算”,即它可以在常量时间内向前或向后跳过任意个元素。该分类下有vector、deque和string 的迭代器 |
注:输入/输出迭代器只适合一次性操作算法
对于这5种分类,C++标准程序库分别提供专属的卷标结构(tag struct)加以确认:
struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag : public input_iterator_tag {};
struct bidirectional_iterator_tag : public forward_iterator_tag {};
struct random_access_iterator_tag : public bidirectional_iterator_tag {};
这些结构之间的继承关系是 is-a 关系(见条款32):所有的前向迭代器都是输入迭代器。
我们知道 STL 迭代器有着不同的能力,实现 advance 的策略之一是采用“最低但最普及”的迭代器能力,以循环反复递增或递减迭代器。然而这种做法耗费线性时间。我们知道随机访问迭代器支持迭代器算术运算,只耗费常量时间,所以我们希望以下面的方式实现 advance:
template<typename IterT, typename DistT>
void advance(IterI &iter, DistT d)
{
if (iter is a random access iterator)
{
iter += d; //针对随机访问迭代器使用迭代器算术运算
} else {
if (d >= 0) { //针对其它迭代器分类反复调用 ++ 或 --
while(d--) ++iter;
} else {
while (d++) --iter;
}
}
}
在上面实现中要判断 iter 是否为 随机访问迭代器,也就是说需要知道 IterT 类型是否为 random access 类型。这就需要 traits,它允许我们在编译期间获取某些类型信息。
Traits 并不是C++关键字或一个预先定义好的构件;它们是一种技术,也是一个C++程序员共同遵守的协议。这个技术要求之一是,它对内置类型 和 自定义类型 的表现必须一样好。举个例子,如果 advance 收到的实参是一个指针(如const char*)和一个 int,上述 advance 仍然必须有效运作,那意味 traits 技术必须也能够施行于内置类型,如指针身上。
traits必须能够施行于内置类型,意味着“类型内的嵌套信息”这种东西没用了,因为我们无法将信息嵌套于原值指针内。因此类型的 traits信息必须位于类型自身之外。标准技术是把它放进一个 模板 及其一个或多个特化版本中。这样的 模板 在STL中有若干个,其中迭代器的为 iterator_traits
:
template<typename IterT>//用来处理迭代器分类
struct iterator_traits;
iterator_traits
是一个 struct 。习惯上 traits 总是被实现为 struct,但它们往往又被称为 traits classes。
iterator_traits
的运作方式是,针对每一个类型为 IterT,在 struct iterator_traits<IterT>
内一定声明某个 typedef 名为 iterator_category
。这个 typedef 用来确认 IterT 的迭代器分类。
综上所述,iterator_traits
首先要求每一个“用户自定义的迭代器类型”必须嵌套一个 typedef,名为 iterator_category
,用来确认适当的 卷标结构:
template< ... > //略而未写 template参数
class deque{ //deque的迭代器
public:
class iterator{
public:
typedef random_access_iterator_tag iterator_category;
...
};
...
};
template< ... >
class list{ //list的迭代器
public:
class iterator{
public:
typedef bidirectional_iterator_tag iterator_category;
...
};
...
};
template<typename IterT>//IterT的iterator_category就是用来表现IterT说自己是什么
struct iterator_traits{
//typedef typename的使用,见条款42
typedef typename IterT::iterator_category iterator_category;
...
};
这样对用户自定义类型行得通,但是对指针(也是一种迭代器)行不通,因为指针不可能嵌套 typedef。下面就是iterator_traits
的第2部分了,专门用来支持指针。
为了支持指针迭代器,iterator_traits
特别针对类型提供一个偏特化版本:
template<typename IterT>
struct iterator_traits<IterT*>//针对内置指针
{
typedef random_access_iterator_tag iterator_category;
...
};
现在可以知道如何设计并实现一个traits class了:
- 确认若干我们希望将来可取得的类型相关信息。例如对迭代器来说,就是可以取得其分类
- 为该信息选择一个名称(例如
iterator_category
) - 提供一个 template 和一组特化版本,内含你希望支持的类型和相关信息
现在有了 iterator_traits
,可以实现一下advance了,对于之前advance 中的伪代码,替换为:
template<typename IterT, typename DistT>
void advance(IterT& iter,DisT d)
{
if(typeid(typename std::iterator_traits<IterT>::iterator_category) ==
typeid(std::random_access_iterator_tag))
...
}
虽然逻辑是正确,但并非是我们想要的,抛开编译问题不说(见条款48),还有一个更根本的问题:IterT 类型在编译期得知,所以 iterator_traits::iterator_category
也在编译期确定。但是 if 语句却是在运行期核定。可以在编译期完成的事情却推到运行期,这不仅浪费时间,还造成执行文件膨胀。
要在编译期确定,可以使用重载。重载是在编译期确定的,编译器会找到最匹配的函数来调用。为了让 advance 的行为如我们所期望,我们需要做得是产生两版重载函数,内含 advance 的本质内容,但各自接受不同类型的 iterator_category
对象,这个函数取名为 doAdvance:
template<typename IterT, typename DisT>
void doAdvance(IterT& iter, Dist d, std::random_access_iterator_tag) { //随机访问迭代器
iter += d;
}
template<typename IterT, typename DisT>
void doAdvance(IterT& iter, Dist d, std::bidirectional_iterator_tag) { //双向迭代器
if(d >= 0)
while(d--) ++iter;
else
while(d++) --iter;
}
template<typename IterT, typename DisT>
void doAdvance(IterT& iter, Dist d, std::input_iterator_tag) { //输入迭代器
if(d < 0)
throw std::out_of_range("Negative distance");
while(d++) --iter;
}
//在advance中调用doAdvance
template<typename IterT,typename DistT>
void advance(IterT& iter,DistT d) {
doAdvance(iter,d,typename std::iterator_traits<IterT>::iterator_category());
}
由于前向迭代器继承自输入迭代器,所以上述的输入迭代器版本也适用于前向迭代器。
现在来总结一下如何使用traits class
- 建立一组重载函数或函数模板(例如doAdvance),彼此间差异只在于各自的traits参数。令每个函数实现与其接受的traits信息匹配
- 建立一个控制函数或函数模板(例如advance),调用上面的函数并传递traits class的信息
Traits广泛应用标准程序库,除了上面所说的iterator_traits,还有char_traits用来保存字符类型相关信息,numeric_limits用来保存数值类型相关信息。
Note:
- Traits classes 使得“类型相关信息”在编译器可用。它们以 template 和 “template特化”完成实现
- 整合重载技术后,Traits classes 有可能在编译器对类型执行 if-else测试