STL随感
map映射类
概念:函数子(functor),比较子(comparator)。函数子是对象化的算法。比较子是一种函数子。关于比较子,用到了一个比较重要的C++技术,就是重载括号运算符。这个运算符的重载一般很少用到,它的思想是把函数调用演化成对象的运算符操作。这样就实现了算法的对象化。
将函数(算法)参数化并非STL的创新,在C中就已经有函数指针。例如一个小于运算的函数过程:
struct { int v1; int v2; } mydata;
bool my_lt(const mydata *d1, const mydata *d2)
{
return (d1->v1 < d2->v1 || d1->v1 == d2->v1 && d1->v2 < d2->v2); // 以v1作为优先比较。
}
对应的函数指针类型:
typedef bool (*lt_int_proc)(int left, int right);
这样,如果又有要求以v2作优先比较,则可以另写一个函数,并将该函数指针作为参数传给高一级的算法。
但是这显然有一个问题,即这种比较限制在mydata类型,远远无法满足面向对象的基本要求。
对于OOP语言,解决这种困难的办法应该有很多,但有两个非常具有代表性的方向,其中之一是动态类型,另一个就是泛化类型。
我所了解的动态类型的代表是Java和Delphi,当然Delphi没有Java彻底。其思想用C++表述就是:
先定义一个公共基类:
class Object;
这样对于比较方法,可以写成:
typedef bool (*lt_proc)(const Object *left, const Object *right);
但事实上并不是这样的C形式的赤裸裸的表现(因为C的函数参数如果不加处理代码还是静态的),而是通过OOP的虚函数机制,实现运行期的自动化。
当然对于传统的C++实现,也是如此:
bool operator < (const Type &left, const Type &right);
在虚函数的问题上C++和Java是一致的,其实现依靠对象指针和一些RTTI。
对于不同的实现,在函数体中先要进行适当的类型转换。
相对于这种动态方法,STL则是另一个极端,它提供的完全是静态方法:
struct lt_my {
bool operator()(const mydata &left, const mydata &right)
{
return (left.v1 < right.v1 || left.v1 == right.v1 && left.v2 < right.v2); // 以v1作为优先比较。
}
};
这个对不使用STL情况下少见的运算符重载还并非最古怪的,最让人拍案叫绝的是这里:
map<const my_data, int, lt_my> mymap; // 将my_data类型的键对应到int类型的值的映射表
由于lt_my是一个类型,于是能作为模版参数传递,而在map的类中将创建一个lt_my的实例comp作为成员,应用算法,就是调用comp.operator ()(a, b),而在书写上只要comp(a, b)即可,这个成员就是名副其实的比较子了。察看了一下,发现comp成员占用了1个字节的空间,如果函数指针,
至少也要4字节,况且函数指针(变量)是不能通过模版参数传入的。
这样,一开始我觉得奇怪的为什么不重载"<"运算符,也就见怪不怪了。
动态的方法体现OOP将数据和算法结合的理念,而STL的做法,显然给了算法更高的重视,想想哪来几个不同的"<"重载让你实现。
类_Tmap_traits一瞥,摘自Visual C++.net 2003的STL版本:
template<class _Kty, // 键类型
class _Ty, // 值类型
class _Pr, // 比较子
class _Alloc, // 分配子
bool _Mfl // 是否允许相同键
>
class _Tmap_traits {
typedef _Kty key_type; // 键类型重定义
typedef pair<const _Kty, _Ty> value_type; // 映射为一个对
typedef _Pr key_compare; // 比较子重定义
// ... 其他内容
protected:
_Pr comp; // 比较子的实例作为数据
};