STL是以泛化原则为基础的:数组被泛化为“以其包含的对象的类型为参数”的容器。函数被泛化为“以其使用的迭代器的类型为参数”的算法,指针被泛化为“以其指向的对象的类型为参数”的迭代器。
这仅仅只是开始。容器类型被泛化为序列和关联容器,类似的容器被赋予相似的功能:
- 标准的连续内存容器提供了随机访问迭代器,而基于节点的容器提供了双向迭代器
- 序列容器支持push_front/push_back操作,而关联容器则不然
- 关联容器提供了对数实践的lower_bound、upper_bound和equal_range成员函数,但是序列容器却没有提供
随着这样的泛化的不断进行,你自然也想加入到这场运动中来。这种想法是值得赞赏的。当你编写自己的容器、迭代器和算法时,你当然想这么做。可很多程序员却以一种不同的方法做泛化。它们在自己的软件中不是针对某种具体的容器,而是想把容器的概念泛化,这样它们做能使用,比如vector,而保留以后使用deque或者list的选择---但不必改变使用该容器的代码。业就是说,他们试图编写独立于容器的代码。这类泛化,尽管出发点是好的,但是几乎总是误入歧途。
即使是最热心的倡导独立于容器类型的代码的人很快就会意思到,试图编写对序列容器和关联容器都适用的代码是毫无意义的。很多成员函数仅当容器为某一类型时才存在。比如,只有序列容器才支持push_front或push_back,只有关联容器才支持count和lower_bound.....。即使是insert这样的基本操作,也会随容器类型的不同而表现出不同的原型和语义。比如,当你向序列容器插入对象时,该对象位于被插入的位置处,而对于关联容器,会按照排序规则,将该对象移动到适当的位置上......
也就是说,我们根据现实,选择合适的容器,不能写一些通用的。
有个问题,如果有一天你发现自己所选择的容器类型不是最佳的,所以你想要使用另一种容器。这时你可以使用常规的方式来是实现这种转变:使用封装技术。最简单的方式是通过对容器类型和其迭代器类型使用typedef。因此,不要这样写:
class Widget{...};
vector<Widget> vw;
Widget bestWidget;
...
vector<Widget>::iterator i = find(vw.begin(), vw.end(), bestWidget);
而要这样写:
class Widget{...};
typedef vector<Widget> WidgetContainer; //...
typedef WidgetContainer::iterator WCIterator; //...
WidgetContainer cw; //..
Widget bestWidget;
...
WCIterator I = find(cw.begin(), cw.end(), bestWidget); //..
这样就使得改变容器类型要容易多了,尤其是当这种改变仅仅是增加一个自定义的分配子时,就更为方便(这一改变不影响使迭代器/指针/引用无效的规则):
class Widget{...};
template<typename T> //...
SpecialAllocator{...} // ...
typedef vector<Widget, SpecialAllocator<Widget>> WidgetContainer; // ...
typedef WidgetContainer::iterator WCIterator;
WidgetContainer cw;
Widget bestWidget;
...
WCIterator I = find(cw.begin(), cw.end(), bestWidget);
类型定义不过是其他类型的别名,所以它带来的封装纯粹是词法上的。类型定义并不能阻止一个客户去做他们原本无法做到的事情。如果你不想把自己选择的容器暴露给客户,就得多费点劲儿。你需要使用类。
要想减少在替换容器类型时所需要修改的代码,你可以把容器隐藏到一个类中,并尽量减少那些通过类接口(而使外部)可见的、与容器相关的信息。比如,当你想创建一个顾客列表时,不要直接使用list。相反,创建一个CustomerList类,并把list因此在其中:
class CustomerList{
private:
typedef list<Customer> CustomerContainer;
typedef CustomerContainer::iterator CCIterator;
CustomerContainer customers;
public:
... //尽量减少那些通过该接口可见的,并且与list相关的信息
};