最近在学习《STL源码剖析》,对书中介绍的traits很有感。
自己在写一下,加深自己的理解。
举例:加入我们现在有一个算法,可以对传入的迭代器前进N步。
template <typename ITERATOR>
void move_n_step(ITERATOR &itor, int n)
{
for (int i = 0; i < n; i++)
itor++;
}
代码很了然,然后我们有两个迭代器,分别是链表迭代器和数组迭代器。
class list_itor
{
public:
void operator++(int)
{
std::cout << "list_iter: " << __func__ << std::endl;
}
};
class array_itor
{
public:
void operator++(int)
{
std::cout << "array_iter: " << __func__ << std::endl;
}
};
为了方便期间,这两个迭代器只实现了后置++操作。
下面是第一版程序:
#include <iostream>
template <typename ITERATOR>
void move_n_step(ITERATOR &itor, int n)
{
for (int i = 0; i < n; i++)
itor++;
}
class list_itor
{
public:
void operator++(int)
{
std::cout << "list_iter: " << __func__ << std::endl;
}
};
class array_itor
{
public:
void operator++(int)
{
std::cout << "array_iter: " << __func__ << std::endl;
}
};
int main()
{
list_itor li;
array_itor ai;
move_n_step(li, 2);
move_n_step(ai, 3);
return 0;
}
但是,我们发现,对于数组性迭代器,我们也是采用++的方式前进,而数组型迭代器是支持算数运算的。
所以我们实现了2个算法,一个处理链表迭代器,一个处理数组迭代器。
#include <iostream>
template <typename ITERATOR>
void move_n_step_list(ITERATOR &itor, int n)
{
for (int i = 0; i < n; i++)
itor++;
}
template <typename ITERATOR>
void move_n_step_array(ITERATOR &itor, int n)
{
itor.operator+(n);
}
class list_itor
{
public:
void operator++(int)
{
std::cout << "list_itor: " << __func__ << std::endl;
}
};
class array_itor
{
public:
void operator+(int n)
{
std::cout << "array_itor: " << __func__ << std::endl;
}
};
int main()
{
list_itor li;
array_itor ai;
move_n_step_list(li, 2);
move_n_step_array(ai, 3);
return 0;
}
这下,好了,数组型迭代器可以发挥自己随机访问的优势了,但是,这颗毒瘤留给了使用这些算法的客户。他要时刻记着他用的是链表迭代器还是数组迭代器。
此时,traits登场(应有掌声)
traits就是个特性渣取机,它能识别出来一个迭代器是链表的还是数组的,是不是很NB。其实,说白了,也就那回事。
#include <iostream>
class list_itor
{
public:
void operator++(int)
{
std::cout << "list_itor: " << __func__ << std::endl;
}
};
class array_itor
{
public:
void operator+(int n)
{
std::cout << "array_itor: " << __func__ << std::endl;
}
};
// 定义两种迭代器的类型,仅仅为了实现函数重载
struct list_itor_type {};
struct array_itor_type {};
template <typename ITERATOR>
struct traits
{
};
// 针对链表迭代器的特化
template <>
struct traits<list_itor>
{
typedef list_itor_type itor_type;
};
// 针对数组迭代器的特化
template <>
struct traits<array_itor>
{
typedef array_itor_type itor_type;
};
// 内部方法,不应该暴露
template <typename ITERATOR>
void __move_n_step(ITERATOR &itor, int n, list_itor_type)
{
for (int i = 0; i < n; i++)
itor++;
}
// 内部方法,不应该暴露
template <typename ITERATOR>
void __move_n_step(ITERATOR &itor, int n, array_itor_type)
{
itor.operator+(n);
}
// 外部方法,供客户调用
template <typename ITERATOR>
void move_n_step(ITERATOR &itor, int n)
{
__move_n_step(itor, n, typename traits<ITERATOR>::itor_type());
}
int main()
{
list_itor li;
array_itor ai;
// 客户再也不想要时刻记者用的是什么迭代器了
move_n_step(li, 2);
move_n_step(ai, 3);
return 0;
}
是不是很爽,读STL源码,就会发现,各种容器与迭代器都要实现value_type,pointer_type,difference,reference等,就是这个道理。
总结:为了提高程序效率,有些时候我们需要根据迭代器的不同类型选择不同的实现。但是我们希望这种变化对客户端是透明的,我们希望客户端只负责传递iterator到确定的一个接口,而不需要根据iterator所指对象的类型再选择不同的函数接口。在这样的背景下, 我们的接口实现程序就需要能够根据给定的迭代器得到其指向对象的类型。有些迭代器是类定义的,可以在定义中加上value_type这样的变量,那么直接调用iterator::value_type就可以得到该迭代器所指对象的类型。但是有些迭代器是原生指针类型,比如int *,这种迭代器没有类定义因此也就没法在其定义中加上value_type。为了获取这类迭代器的value_type,我们采取的做法是对迭代器做进一步的封装,我们另外定义一个专门的class template用来萃取迭代器的特性,并且针对类定义的迭代器和其他类型的迭代器做出不同的实现(偏特化),如下:
<pre name="code" class="cpp">template <class T>
struct traits<T>
{
typedef typename I::value_type value_type;
};
template <class T>
struct traits<T*>
{
typedef T value_type;
};
</pre><p></p><pre>