今天被人问到STL中的某一个容器中说定义的成员类型是干嘛的,在这里我总结一下。
C++中的类成员有三部分:成员变量,成员函数,成员类型。
如果在类内部嵌套定义了一个类型,比如MyClass类中有一句 typedef int integer;那么就可以这样来定义变量 MyClass::integer a; 实际上就相当于定义了一个int 类型的变量a。 当然如果MyClass类作为模板的类型参数T的实参,在模板内,需要 typename T::integer a这样使用。
STL很大的一个好处是实现了算法与数据结构的解耦,也就是算法对于STL的容器具有普遍性,实现对容器抽象的通用算法。
比如algorithm中的find()函数,其实现如下:
template<class InputIterator, class T>
InputIterator find (InputIterator first, InputIterator last, const T& val)
{
while (first!=last) {
if (*first==val) return first;
++first;
}
return last;
}
怎么做到对每种容器都适用呢,这就要使用STL中的嵌入的类型信息,也就是靠成员类型来实现。可以为每一种容器都定义Iterator
类型的成员类型,而且该成员类型必须能够进行 != 比较操作,*first解引用操作,++自增操作
;简单的讲就是能够通过这个Iterator
来遍历容器,并且能够访问对应位置的元素。
find 算法的思想很简单,从前到后遍历容器;访问容器中的每个元素与被查找的值进行比较。
问题在于,对于不同的容器,遍历方式不一样;访问下一个元素,对于vector,可能只是内部指针的 ++即可以;对于list,可能是内部指针的 p = p->next; 对于map,可能是内部指针模拟先序遍历等等;那么怎么屏蔽这些差异,实现语法上的一致性呢(consistency is good!),如果容器内部自定义的Iterator
(虽然各自的Iterator
内部实现不一样,封装内部的差异),只要让++操作都能访问到下一个元素,实现遍历不就行了。
所以总结来说,这些成员类型的作用是为了实现接口的统一,使算法对各种容器表现出一致性。也可以说,这些成员类型就相当于一组契约,只要遵守这些契约,那么便可以使用使用了这些契约的算法。
STL对这些契约有很强的依赖。
现在假若我不是对容器操作,也就是说不是对类对象操作,比如是指针,那么显然没办法嵌入类型信息。此时可以使用一个中间层,而不是嵌入的,提供更好的灵活性。这种技术就是traits技术,一个类模板,通过这个中间层类模板提供类型信息的集合。(下边是针对指针的特化版本,普通容器的是非特化版本,可以用来取代嵌入型的类型信息).
比如现在对容器中的每个元素一次进行某种操作:
首先定义traits中间层类型信息集合类模板,然后对指针类型进行特化,于是,可以看到最终的函数具有更多的类型适应性,也就是更加通用的算法,而且对于用户来说,这些都是隐藏的。
template <typename Container>
class ContainerTraits
{
typedef typename Container::Elem Elem;
typedef typename Container::InputIterator InputIterator;
};
template<>
class ContainerTraits<T*>
{
typedef T Elem;
typedef T* InputIterator;
};
template <typename Container>
typename ContainerTraits::Elem process(Container& c, int size)
{
}
通用算法,一直性真是个好东西。