本文相关代码摘录自SGI STL的源码。通过分析源码,更能理解stl中排序算法是怎么软件架构的。
map的sort源码分析
stl_map.h
#ifndef __STL_LIMITED_DEFAULT_TEMPLATES
template <class Key, class T, class Compare = less<Key>, class Alloc = alloc>
#else
template <class Key, class T, class Compare, class Alloc = alloc>
#endif
class map {
public:
// typedefs:
typedef Key key_type;
typedef T data_type;
typedef T mapped_type;
typedef pair<const Key, T> value_type;
typedef Compare key_compare;
从map类的定义可以看出Compare有一个默认的值less实现如下。
template <class T>
struct less : public binary_function<T, T, bool> {
bool operator()(const T& x, const T& y) const { return x < y; }
};
并且定义了一个rb_tree的成员变量,map的方法具体实现都是通过rb_tree t。
private:
typedef rb_tree<key_type, value_type,
select1st<value_type>, key_compare, Alloc> rep_type;
rep_type t; // red-black tree representing map
模板参数select1st<value_type>表示取得value_type类型也即pair的first成员变量。
select1st<value_type>是一个struct类型,使用时用的它的仿函数。
template <class Pair>
struct select1st : public unary_function<Pair, typename Pair::first_type> {
const typename Pair::first_type& operator()(const Pair& x) const
{
return x.first;
}
};
// stl pair源码:
template <class T1, class T2>
struct pair {
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair() : first(T1()), second(T2()) {}
pair(const T1& a, const T2& b) : first(a), second(b) {}
#ifdef __STL_MEMBER_TEMPLATES
template <class U1, class U2>
pair(const pair<U1, U2>& p) : first(p.first), second(p.second) {}
#endif
};
我们通过map的一个简单的insert方案,来探究Compare的使用。
map的insert,实际调用rb_tree的insert_unique方法
pair<iterator,bool> insert(const value_type& x) { return t.insert_unique(x); }
rb_tree的insert_unique方法:
template <class Key, class Value, class KeyOfValue, class Compare, class Alloc>
pair<typename rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::iterator, bool>
rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::insert_unique(const Value& v)
{
link_type y = header;
link_type x = root();
bool comp = true;
while (x != 0) {
y = x;
comp = key_compare(KeyOfValue()(v), key(x));
x = comp ? left(x) : right(x);
}
iterator j = iterator(y);
if (comp)
if (j == begin())
return pair<iterator,bool>(__insert(x, y, v), true);
else
--j;
if (key_compare(key(j.node), KeyOfValue()(v)))
return pair<iterator,bool>(__insert(x, y, v), true);
return pair<iterator,bool>(j, false);
}
class KeyOfValue也即map中定义rb_tree传入的模板参数select1st<value_type>。
KeyOfValue()(v) 确实也是用的struct select1st的仿函数,取Value& v的first值,对于map来说就是取的key值。key(x)取x的first,也即x的key值。
函数中的while循环表示从root开始,如果key_compare是默认传入的less方法, 插入的值v的key值小于节点x的key值,x节点等于x的左叶子,否则x节点等于x的右叶子。
在这里我不去关系rb_tree的实现,只探究Compare的调用逻辑。
总结
分析完源码,我们总结一下map的Compare使用:
- 仿照上面struct less的写法,可以自定义Compare函数,按照key值排序。
- 从源码可以看出,只能按照key值排序,不能按照value排序。
comp = key_compare(KeyOfValue()(v), key(x));
如果想要按照value排序,可以将pair整个当做key值。在Compare仿函数中比较pair的second(value)。
需要注意点
rb_tree的find函数
template <class Key, class Value, class KeyOfValue, class Compare, class Alloc>
typename rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::iterator
rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::find(const Key& k) {
link_type y = header; // Last node which is not less than k.
link_type x = root(); // Current node.
while (x != 0)
if (!key_compare(key(x), k))
y = x, x = left(x);
else
x = right(x);
iterator j = iterator(y);
return (j == end() || key_compare(k, key(j.node))) ? end() : j;
}
return (j == end() || key_compare(k, key(j.node))) ? end() : j;
从这句代码可知,key_compare的结果是true,返回end();key_compare的结果是false,返回 j。
当查找的值k在map中存在时,返回j,不存在时返回end()。
所以key_compare(x, y) x和y相等时,应该返回false。
所以下面的compare写法是错误的:
template <class T>
struct less {
bool operator()(const T& x, const T& y) const { return x <= y; }
};
或是
template <class T>
struct greater {
bool operator()(const T& x, const T& y) const { return x >= y; }
};
正确的实现应该是:
template <class T>
struct less {
bool operator()(const T& x, const T& y) const { return x < y; }
};
或是
template <class T>
struct greater {
bool operator()(const T& x, const T& y) const { return x > y; }
};
使用:
template <class T>
struct compare1 {
bool operator()(const T& x, const T& y) const { return x < y; }
};
template <class T>
bool compare2(const T& x, const T& y) const { return x < y; }
// map 的两个构造函数
map() : t(Compare()) {}
explicit map(const Compare& comp) : t(comp) {}
// 定义一个map对象
std::map<int, int, compare1<int>> mmap;
或者
compare1<int> comp;
std::map<int, int, compare1<int>> mmap(comp);
// 或者
std::map<int, int, bool(*)(const int&, const int&)> mmap(compare2<int>);
// 或者
std::map<int, int, bool(&)(const int&, const int&)> mmap(compare2<int>);
千万注意以下这种写法是错误的,编译就通不过。
std::map<int, int, compare1<int>)> mmap(compare1<int>()); // build error
这里是因为这种写法compare1()会被编译器误认为是一个返回值compare1的函数指针
compare1(*)()。
但是奇怪的是std::sort就可以这样传入没问题,我猜想可能是这种情况编译器对是值传递和引用传递两种传参方式解释有所区别。
其他
在map源码中operator[]的实现,也验证了map调用[]方法如果传入的key值不存在,则插入该key。
operator[]虽然不会造成异常,但是却容易掩盖bug。
at()方法如果传入的key不存在,则抛出异常,也容易暴露出bug。
T& operator[](const key_type& k) {
return (*((insert(value_type(k, T()))).first)).second;
}