尽量不用char*作为hash_map的key
引子:
同事前几天用hash_map时发现一些问题。当时的场景是有一些字符串char*,要去对应某种类型的对象。同事的做法是:
- 尝试用char*作为key进行hash。编译通过,但运行时不正常,insert操作可以成功,但find操作基本都失败
- 改用string将原字符串包装后作为key进行hash。编译时不能通过
- google后,用string作key,并添加了一个template<> struct hash< std::string >的仿函数作为hash_map的构造参数。编译通过,运行正常,但不知原委为何
带着这三个问题去查看了libstdc++中关于hash_map的实现(省略了与讨论无关部分):
// hash_map template<class _Key, class _Tp, class _HashFn = hash<_Key>,
class _EqualKey = equal_to<_Key>, class _Alloc = allocator<_Tp> >
class hash_map
{
private:
typedef hashtable<pair<const _Key, _Tp>,_Key, _HashFn,
_Select1st<pair<const _Key, _Tp> >,
_EqualKey, _Alloc> _Ht; _Ht _M_ht; // ... hash_map()
: _M_ht(, hasher(), key_equal(), allocator_type()) {} // ... _Tp&
operator[](const key_type& __key)
{ return _M_ht.find_or_insert(value_type(__key, _Tp())).second; } // ...
}
line 3~5 可见,hash_map是个模板类,定义了5种参数类型,分别是
- key的类型_Key
- 值的类型_Tp
- hash仿函数_HashFn,用于执行真正的hash操作。有默认模板参数hash<_Key>
- 比较仿函数_EqualKey,用于执行hash冲突后,bucket内的find工作。有默认模板参数equal_to<_Key>
- 内存分配器。有默认模板参数参数
line 8~12可见,hash_map中包含了一个hashtable对象,hashtable也是个模板类,有6个参数类型,参数的具体类型在hashtable.h中,分别是
- hash_table中储存的值的类型_Val,实际对应hash_map中的pair<const _Key, _Tp>
- Key的类型_Key
- hash仿函数
- 从pair对象中分离出key对象的仿函数_ExtractKey
- 比较仿函数_EqualKey
- 内存分配器
line 16~23可见,hash_map其实是对hashtable的包装,其初始化、find与赋值操作都是由内部的hashtable对象来完成的。
hashtable的具体实现是:
- 由一个bucket数组组成
- 每个bucket下面挂着一个hash_node组成的list
- 每个hash_node由一个_Val对象(存储真正元素)和一个hash_node指针(next指针)组成
hashtable的工作过程是:
- 将key用_HashFn进行hash
- 将hash的结果执行取模操作%n(其中n是hashtable中bucket的数目),定位到具体bucket的位置
- 依次用_EqualKey比较bucket中hash_node的key,找到与输入元素相同的node,返回;若找不到,则构造一个node返回
下面来回答篇首提出的三个问题
- 为什么用char*(或const char*)作为key,可以顺利insert,却不能顺利find?
因为insert时,会将char*指针进行hash,默认的内置hash函数接受char*作为参数,并将所指字符串进行hash,直到串尾。因此可以顺利找到bucket,但在进一步查找比对key时,用的是equal_to<char*>函数,它是直接比对指针的!一般来说,进行insert操作时,指针是不相同的,因此每次insert都生成新的node返回,insert正确。用size()方法也可以验证到,确实能够insert成功。
而在find操作中(假设用来insert的key已经在hash表中,本应可以命中的),同理可以找到bucket,但是在比对key时用的是char*指针,而实情却是char*所指的内容相同!但equal_to<char*>不会理会这些,它只是傻傻比对指针,因此基本不会找到结果。(假如可以找到结果,那就是hash表中存的char*和你输入的char*正好相同)
- 为什么改用string作为key,会无法通过编译?
因为默认的内置hash函数不接受string作为参数,也就是说,没有hash(string str)或者hash(const string& str)这种特化存在。其实,hash函数支持的函数是相当有限的,仅有char、int、long以及它们的const和unsigned版本,指针类型更是只支持char*!
- 为什么加上template<> struct hash< std::string >的实现,就可以编译并执行正确?
首先加上这个hash后,hash函数能够处理string参数,并正确找到bucket,在比对key环节,用的是默认的equal_to<string>,而这个函数可以正确来比对字符串而不是比对指针,因此insert和find都能成功。
至此,总结下以后遇到这种情况怎么办。有两种方法:
- 写一个关于字符串的比较函数(类似于strcmp就可以),构造hash_map时传进去,保证在key比对时不是对比较指针而是比较字符串
- 写一个接受string类型的hash函数,保证hash时string参数能被正确处理
事情到此时貌似已经圆满了。连《STL源码剖析》P278也说这样OK。但是……真的是这样吗?char*真的能用来作hash_map的key吗?
答案是不可以!!!这里存在一个巨大的隐患:
hash表中存储的永远是pair<_Key, _Tp>,如果用char*作key,则存储的key只能是char*。这里就涉及到一个内存管理的问题,你要确保之前insert时用的char*不能失效,而且内容不能被更改。否则在bucket内比对key时就会出现严重的问题,轻则找不到元素,甚至core掉。
即使是用const char*作key同样不安全,因为一旦const char*的生命期比hash_table短,那么hash_table中相应的key就已变为野指针。
验证:
#include <iostream>
#include <ext/hash_map>
using namespace __gnu_cxx;
#include <function.h>
#include <cstring>
using namespace std; struct mystrcmp
{
bool operator()(const char* s1, const char* s2)
{
return strcmp(s1, s2) == ;
}
}; int main()
{
hash_map<char*, int, hash<char*>, mystrcmp> days; days["Mon"] = ;
days["Tue"] = ;
cout << "now there is " << days.size() << " in hash_map" << endl; days.clear(); char mon[] = {};
char tue[] = {};
strcpy(mon, "Mon");
strcpy(tue, "Tue");
days[mon] = ;
days[tue] = ;
cout << "now there is " << days.size() << " in hash_map" << endl; char someday[] = {};
strcpy(someday, "Mon");
strcpy(mon, "Mon1"); cout << "now there is " << days.size() << " in hash_map" << endl;
cout << "Mon " << days[someday] << endl;
cout << "now there is " << days.size() << " in hash_map" << endl;
}
now there is 2 in hash_map
now there is 2 in hash_map
now there is 2 in hash_map
Mon 0 <<----这里竟然没有查找成功
now there is 3 in hash_map <<----而是执行了插入操作
结论:
- 不用char*或const char*作为hash_map的key。用string包装并代替它,同时为hash仿函数添一个string的特化版本
- 一定要用char*的话,请用const char*,还要保证在hash_map的生命周期里,曾经insert过的const char指针不要变成野指针
- 尝试用unordered_map代替hash_map。首先它原生支持string,其次有效率优势,再次已经成为新标准,便于扩展。hash_map已经被放到backward里
尽量不用char*作为hash_map的key的更多相关文章
- 不用char*作为hash_map的key
尽量不用char*作为hash_map的key Posted on 2013-09-09 21:21 Springlie 阅读(83) 评论(0) 编辑 收藏 引子: 同事前几天用hash_map时发 ...
- linux下C++ STL hash_map的使用以及使用char *型变量作为Key值的一大“坑”
计算机编程中经常会用到hash表,而在C++中,使用STL编程更是少不了的.本文将介绍STL中hash_map的使用.在hash_map中使用自定义类型作为key值的方法以及在使用char *类型作为 ...
- pig的grunt中shell命令不稳定,能不用尽量不用
shell命令:mv a b 将文件a改名为b, 可如果b已经存在,比如/test文件下有a和b两个文件,执行mv a b后,b被覆盖的了.也就是/test文件下只有a. 但是mv命令在pig的g ...
- hibernate部分源码解析and解决工作上关于hibernate的一个问题例子(包含oracle中新建表为何列名全转为大写且通过hibernate取数时如何不用再次遍历将列名(key)值转为小写)
最近在研究系统启动时将数据加载到内存非常耗时,想着是否有办法优化!经过日志打印测试发现查询时间(查询时间:将数据库数据查询到系统中并转为List<Map>或List<*.Class& ...
- 以后尽量不用cin、cout啦
cout输出有问题(对于double,不同OJ处理的结果不一样),cin读入机制较scanf繁琐.慢!!!!!!!!
- 138.安全退出的异常,要用throw 尽量不用exit(0)
#include<iostream> #include<cstdlib> using namespace std; ////非安全退出,结束进程, //C++ 必须释放对象,最 ...
- c++动态内存分配需要注意的地方,可以不用尽量不用
在实际开发中,需要动态分配内存的场景极少,string和vector已经足够方便,如果不是非用不可的情况,采用动态分配内存就是给自己挖坑. 如果应用开发中一定要用动态内存分配技术,建议把它封装在类中, ...
- C++中的hash_map和map的区别
hash_map和map的区别在哪里?构造函数.hash_map需要hash函数,等于函数:map只需要比较函数(小于函数). 存储结构.hash_map采用hash表存储,map一般采用红黑树(RB ...
- SQL Server中char、varchar、text和nchar、nvarchar、ntext的区别 (转)
转:http://blog.csdn.net/jackychu/article/details/4183118 http://www.cnblogs.com/jhxk/articles/1633578 ...
随机推荐
- 谷歌、火狐浏览器下实现JS跨域iframe高度自适应的完美解决方法,跨域调用JS不再是难题!
谷歌.火狐浏览器下实现JS跨域iframe高度自适应的解决方法 导读:今天开发的时候遇到个iframe自适应高度的问题,相信大家对这个不陌生,但是一般我们都是在同一个项目使用iframe嵌套页面,这个 ...
- Android中适用于ListView、GridView等组件的通用Adapter
今天随便逛逛CSDN,看到主页上推荐了一篇文章Android 高速开发系列 打造万能的ListView GridView 适配器,刚好这两天写项目自己也封装了相似的CommonAdapter,曾经也在 ...
- SDUT 1124-飞跃荒野(三维BFS)
飞跃原野 Time Limit: 5000ms Memory limit: 65536K 有疑问?点这里^_^ 题目描写叙述 勇敢的法里奥出色的完毕了任务之后.正在迅速地向自己的基地撤退.但因为 ...
- 使用myeclipse创建带注解的model实体类
1.先新建JPA项目: 如果没有就点击左下角的Show All Wizards. 点两次Next后,点击Finish即可,中间不用任何操作 (点第二次Next后会出现连接到所在数据库,先不管) ...
- Jenkins + robot framework自动发送邮件报告
一.Jenkins安装插件 进入系统管理—插件管理—可选插件下安装以下插件Email-ext plugin.Email-ext Template Plugin. 安装完如下: 二.系统设置 1.设置系 ...
- Samza/KafkaAnalysizing
Apache Samza is a distributed stream processing framework. It uses Apache Kafka for messaging, and A ...
- APMServ—我用过的最优秀的PHP集成环境工具
原文:APMServ-我用过的最优秀的PHP集成环境工具 经常折腾wordpress和各种cms,免不了要在本地测试一些程序,所以选择一款好的php集成环境就至关重要啦. 1. 我用过的php集成环境 ...
- ZOJ 2675 Little Mammoth(计算几何)
圆形与矩形截面的面积 三角仍然可以做到这一点 代码: #include<stdio.h> #include<string.h> #include<stdlib.h> ...
- [强烈推荐]ORACLE PL/SQL编程详解之七:程序包的创建与应用(聪明在于学习,天才在于积累!)
原文:[强烈推荐]ORACLE PL/SQL编程详解之七:程序包的创建与应用(聪明在于学习,天才在于积累!) [强烈推荐]ORACLE PL/SQL编程详解之七: 程序包的创建与应用(聪明在于学习,天 ...
- 从.net复制源代码中国农历阵列,必要做日历
从.net复制源代码中国农历阵列,必要做日历 const { 闰月的月份.春节的阳历日期(农历正月初一).农历的每一个月天数 } c_arrLunarInfo: array [1900 .. 2100 ...