Item 19:区分等价(equivalence)和相等(equality)。
Item 20:为成员是指针类型的关联容器提供比较类型。
Item 21:当对象相等时,总是让比较函数返回 false。
Item 23:考虑用 sorted vector 代替关联容器。
Item24:在效率很重要的情况下,仔细选择 map::operator[] 和 map::insert。
Item 25:熟悉非标准的散列容器(hashed containers)。
- 现看看英汉字典对等价(equivalence)的定义:
一种逻辑操作符,具有下述性质:若P是一个命题,Q是一个命题,R是一个命题,当且仅当 所有的命题为真或所有的命题为假时,P,Q,R,...的等价才为真。
- 在 STL 中有两种比较对象是否相等的方法,方法一,相等(equality):
x == y
- 方法二,等价(equivalence):
!(x < y) && !(y < x)
- 标准关联容器需要对元素进行排序,对于每个容器必须有一个比较算法(比较对象的大小,而不仅仅是是否相等),默认的算法是 less,用户必须定义 operator < 及 operator == 。为了减少冲突和减少用户的编码量,STL 用等价代替相等,这样用户即可将 operator == 省略。
- 总结:关联容器使用等价,顺序容器使用相等。
Item 20:为成员是指针类型的关联容器提供比较类型。
- stl::set<typename T, typename Compare> 其中 Compare 是一个 type 而不是一个函数指针、或者函数对象,这就是所谓的“比较类型(comparison types)”。
Item 21:当对象相等时,总是让比较函数返回 false。
- 当比较函数是 < 或 > 时,此条目是废话。
- 当比较函数是 <= 或 >= 时,如果 x == y 时使 <= 及 >= 返回 true,则会破坏等价性(见Item 19),从而破坏关联容器。
x == y 与 !(x <= y) && !(y <= x) 冲突。
Item 22:避免对 set 及 multiset 的关键部分进行 in-place 修改。- 首先看看 wikipedia 对 in-place 的定义:
In computer science, an in-place algorithm is an algorithm which transforms a data structure using a small, constant amount of extra storage space. The input is usually overwritten by the output as the algorithm executes.
- 这里的“关键部分(key part)”指的是 set / multiset 存储的对象 T 中对 set / multiset 的排序算法有影响的部分,或者说是参与排序的部分。比如下例中 User::ID:
class User {
public:
unsigned int ID;
std::string name;
unsinged int age;
};
class UserIDLess : public binary_function<User, User, bool> {
public:
operator() (const User &lhs, const User &rhs) const {
return lhs.ID < rhs.ID;
}
};
typedef set<User, UserIDLess> IDSortedUserSet;
public:
unsigned int ID;
std::string name;
unsinged int age;
};
class UserIDLess : public binary_function<User, User, bool> {
public:
operator() (const User &lhs, const User &rhs) const {
return lhs.ID < rhs.ID;
}
};
typedef set<User, UserIDLess> IDSortedUserSet;
- 修改 set / multiset 中的对象时,注意不要改变对象的关键部分,否则容器会被破坏。
- 如果必须改变关键部分,采取如下策略:
1. 找到要修改的对象。*a = set.find();
2. 复制对象。*b = new Obj(*a);
3. 修改复制的对象。b->changeSome();
4. 删除原对象。set.remove(a);
5. 插入复制的对象。set.insert(b);
2. 复制对象。*b = new Obj(*a);
3. 修改复制的对象。b->changeSome();
4. 删除原对象。set.remove(a);
5. 插入复制的对象。set.insert(b);
Item 23:考虑用 sorted vector 代替关联容器。
- 关联容器对频繁的插入、删除、查找作了优化。但我们的操作模式通常是:首先建立容器(向容器中插入数据),然后使用容器(在容器中查找对象,多次),修改容器(如果有必要)。在这样的情况下我们可以使用 sorted vector 替代关联容器。
- 理由一:关联容器的内部实现是平衡二叉树,比 vector 所占用的内存要大,特别是在对象较小而数量众多的情况下。
- 理由二:当占用内存数超过一个物理内存页面(4K )大小时,关联容器的各种算法均会导致频繁的页交换,而 vector 则会好的多。
Item24:在效率很重要的情况下,仔细选择 map::operator[] 和 map::insert。
- map::operator[] 采用的是 "add or update" 策略。
- 当指定键值不存在时,map[key] = value 与下例等同:
pair<Map::iterator, bool> result =
map.insert(Map::value_type(key, MapValue());
result.first->second = value;
map.insert(Map::value_type(key, MapValue());
result.first->second = value;
- 效率比 map::insert 差,替代方案:
template<typename Map, typename MapKey, typename MapValue>
typename Map::iterator
efficientAddOrUpdate(Map& map, const MapKey &key, const MapValue value) {
typename Map::iterator itr = map.lower_bound(key);
if (itr != map.end() && !(map.key_comp()(key, itr->first))) {
itr->second = value;
return itr;
}
else {
return map.insert(itr, Map::value_type(key, value));
}
}
typename Map::iterator
efficientAddOrUpdate(Map& map, const MapKey &key, const MapValue value) {
typename Map::iterator itr = map.lower_bound(key);
if (itr != map.end() && !(map.key_comp()(key, itr->first))) {
itr->second = value;
return itr;
}
else {
return map.insert(itr, Map::value_type(key, value));
}
}
- 上面使用了 hinted insert,有较高效率。
Item 25:熟悉非标准的散列容器(hashed containers)。
- 为了不推迟 STL 的发布,标准委员会没有将 hashed container 放入标准中。(下一版会加入?)
- 但我们有事实上的 hash 容器:hash_set, hash_multiset, hash_map, hash_multimap.