<?xml:namespace prefix = o />
自从C++中引入了template后,以泛型技术为中心的设计得到了长足的进步。STL就是这个阶段杰出的产物。STL的目标就是要把数据和算法分开,分别对其进行设计,之后通过一种名为iterator的东西,把这二者再粘接到一起。设计模式中,关于iterator的描述为:一种能够顺序访问容器中每个元素的方法,使用该方法不能暴露容器内部的表达方式。可以说,类型萃取技术就是为了要解决和iterator有关的问题的,下面,我们就来看看整个故事。
应该说,迭代器就是一种智能指针,因此,它也就拥有了一般指针的所有特点——能够对其进行*和->操作。但是在遍历容器的时候,不可避免的要对遍历的容器内部有所了解,所以,设计一个迭代器也就自然而然的变成了数据结构开发者的一个义务,而这些iterators的表现都是一样的,这种内外的差异,对用户来说,是完全透明的,
第一部分 为什么要有萃取技术
既然是一种智能指针,iterator也要对一个原生指针进行封装,而问题就源于此,当我们需要这个原生指针所指对象的类型的时候(例如声明变量),怎么办呢?
Case1 对于函数的局部变量
这种情况我们可以采用模版的参数推导,例如:
template <class T> void func(T iter)
如果T是某个指向特定对象的指针,那么在func中需要指针所指向对象类型的变量的时候,怎么办呢?这个还比较容易,模板的参数推导机制可以完成任务,如下:
template <class T, class U>
void func_impl(T t, U u) {
U temp; // OK, we’ve got the type
// The rest work of func…
}
template <class T>
void func(T t) {
func_impl(t, *t); // forward the task to func_impl
}
通过模板的推导机制,我们轻而易举的或得了指针所指向的对象的类型,但是事情往往不那么简单。例如,如果我想把传递给func的这个指针参数所指的类型作为返回值,显然这个方法不能凑效了,这就是我们的case 2。
Case2 对于函数的返回值
尽管在func_impl中我们可以把U作为函数的返回值,但是问题是用户需要调用的是func,于是,你不可能写出下面的代码:
template <class T, class U>
U func_impl(T t, U u) {
U temp; // OK, we’ve got the type
// The rest work of func…
}
template <class T>
(*T) func(T t) { // !!!Wrong code
return func_impl(t, *t); // forward the task to func_impl
}
int i =10;
cout<<func(&i)<<endl; // !!! Can’t pass compile
红色的部分概念上如此正确,不过所有的编译器都会让你失望。这个问题解决起来也不难,只要做一个iterator,然后在定义的时候为其指向的对象类型制定一个别名,就好了,像下面这样:
template <class T>
struct MyIter {
typedef T value_type; // A nested type declaration, important!!!
T* ptr;
MyIter(T* p = 0) : ptr(p) {}
T& operator*() const { return *ptr; }
};
而后只要需要其指向的对象的类型,只要直接引用就好了,例如:
template <class I>
typename I::value_type func(I iter) { return *iter; }
很漂亮的解决方案,看上去一切都很完美。但是,实际上还是有问题,因为func如果是一个泛型算法,那么它也绝对要接受一个原生指针作为迭代器,但是显然,你无法让下面的代码编译通过:
int *p = new int(52);
cout<<func(p)<<endl; // !!!Is there a int::value_type?? Wrong Code here
我们的func无法支持原生指针,这显然是不能接受的。此时,template partial specialization就派上了用场。
Solution:template partial specialization是救世主
既然刚才的设计方案仍不完美,那我们就再加一个间接层,把智能指针和原生指针统统的封装起来。在讨论之前,先要澄清一下template partial specialization的含义。所谓的partial specialization和模板的默认参数是完全不同的两件事情,前者指的是当参数为某一类特定类型的时候,采用特殊的设计,也就是说是“针对template参数更进一步的条件限制所设计出来的一个特化版本”;而默认参数则是当不提供某些参数的时候,使用的一个缺省。
参考:partial specialization的语法
Template <typename T> class C<T*> {} // 为所有类型为T*的参数而准备的特殊版本
好了,下面我们就找一个专职的负责人,用来封装迭代器封装的对象类型。首先,把我们刚才的MyIter重新包装一下:
template <class I>
struct iterator_traits {
Typedef I::value_type value_type;
}
现在,我们的func又有了新的面貌:
template <class I>
typename iterator_traits<I>::value_type func(I ite) {
return *ite;
}
尽管这次我们的函数返回值的长度有些吓人,但是,我们的确为原生指针找到了好的解决方案。只要为原生指针提供一个偏特化的iterator_traits就OK了。如下:
template <class I>
struct iterator_traits<T*> {
typedef T value_type;
};
下面,我们终于可以让func同时支持智能指针和原生指针了:
template <class I>
struct iterator_traits {
Typedef I::value_type value_type;
}
template <class T>
struct iterator_traits<T*> {
typedef T value_type;
};
template <class I>
typename iterator_traits<I>::value_type func(I ite) {
return *ite;
}
int main() {
MyIter<int> iter = new int(520);
int *p = new int(520);
// This time the following two statements will success
cout<<func(iter)<<endl;
cout<<func(p)<<endl;
return 0;
}
但是,我们离最后的成功还有最后一步,如果,我们需要声明一个value_type类型的左值,但是却给iterator_traits传递了一个const int*,显然结果有问题,于是,为const T*也另起炉灶,准备一份小炒:
template<class T>
struct iterator_traits<const T*> {
typedef T value_type;
}
OK,现在万事大吉,无论是正宗迭代器,原生指针,const原生指针,我们都可以利用iterator_traits萃取出其封装的对象的类型,萃取技术由此而来。