这么一段程序,
1 2 3 4 5 6 7 8 9 | typedef __gnu_cxx::hash_map<object *, object *, object_equal_to, object_hasher> object_hash_map_t; object_hash_map_t hash_map; hash_map.insert(make_pair(new object, new object)); object_hash_map_t::iterator it = hash_map.begin(); while (it != hash_map.end()) { delete it->first; delete it->second; ++it; } |
该程序core而不止。因为STL的代码很多都被内联了,调试起来不方便,于是看了下sgi的STL实现,发现了问题。首先,STL的hash_map是由hashtable实现的,hash_map只是对hashtable的一个封装。hashtable也只是很普通的实现,使用单链表解决冲突。下面是hashtable中对迭代器的operator++操作的实现,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | struct _Hashtable_node { _Hashtable_node *_M_next; _Val _M_val; //~ pair<Key, Value> }; typedef _Hashtable_node _Node; //~ omitting the template arguments struct _Hashtable_iterator { _Node *_M_cur; _Hashtable *_M_ht; _Hashtable_iterator& operator++() { const _Node* __old = _M_cur; //! preserve the old node _M_cur = _M_cur->_M_next; if (!_M_cur) { size_type __bucket = _M_ht->_M_bkt_num(__old->_M_val); //! get bucket number with hash key while (!_M_cur && ++__bucket < _M_ht->_M_buckets.size()) _M_cur = _M_ht->_M_buckets[__bucket]; } return *this; } }; |
了然了,iterator的operator++需要根据key计算出迭代器当前所在的bucket,然后在此bucket及后续bucket中顺序遍历以寻找下一个元素。所以在开头的示例代码中,由于在迭代过程中释放了key的内存,当迭代器“递增”计算当前bucket number时当然就core掉了。
hash_map在STL中已经被标记为deprecated,已经过时了,若要使用hash_map,建议使用具有相同接口和特性的unordered_map。
另外,使用hash_map时,一定要注意模板参数equal_to和hash_func,前者用来判断key是否相等,后者用来计算key的hash值,稍有不慎就会出现莫名其妙的错误,例如著名的hash_map的遍历死循环问题。