CMap在用CString做key类型时,ARG_KEY要选LPCTSTR

2024-04-03 07:18

本文主要是介绍CMap在用CString做key类型时,ARG_KEY要选LPCTSTR,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

CMap在用CString做key类型时,ARG_KEY要选LPCTSTR 
文章来源:http://blog.csdn.net/flyingxu/archive/2005/12/26/562852.aspx

遇到好几个人说CMap在用CString做key类型时有问题,说用int和DWORD就可以,用CString就不行。因此很多人推荐使用MFC中的CMapStringToPtr之类。

看下面的代码:
//.h
    CMap<CString, LPCTSTR, int, int> typeMap;
//.cpp
    typeMap.SetAt(_T("ONE"),1);
    typeMap.SetAt(_T("TWO"),2);

    int nValue = 0;
    BOOL ret = typeMap.Lookup(_T("ONE"), nValue);
    ret = typeMap.Lookup(_T("THREE"), nValue);
    ret = typeMap.Lookup(_T("TWO"), nValue);
我的代码运行的很好,我觉得关键是ARG_KEY要选LPCTSTR

原因:  referencehttp://www.codeproject.com/KB/architecture/cmap_howto.aspx

Introduction

Programmers like me, who learnt STL::map before CMap, always think CMap is difficult to use, and always try to useCMap in the way as a STL::map. In this article, I will explain about CMap and what you should do to use it for your own custom classes. And at the end of this article, I will show an example of how to use CMap correctly withCString* (note, I mean CString pointer and not CString :>)

CMap Internal

The first thing to be noted is that CMap is actually a hash map, and not a tree map (and usually a Red-black tree) asSTL::map. Shown below is the internal structure of a CMap.

How to declare a CMap

Many people get confused about CMap's declaration CMap<KEY, ARG_KEY, VALUE, ARG_VALUE>, why not justCMap<KEY, VALUE>?

In fact, the ultimate data container in CMap is CPair, and the internal of CPair is {KEY, VALUE}. Therefore, CMapwill really store a KEY, and not ARG_KEY. However, if you check with the MFC source code, almost all the internal parameters passing within CMap itself is called with ARG_KEY and ARG_VALUE, therefore, using KEY& as ARG_KEYseems always a correct thing, except when:

  1. You are using primitive date types like intchar, where pass-by-value makes no difference (may be even faster) with pass-by-reference.
  2. If you use CString as KEY, you should use LPCTSTR as ARG_KEY and not CString&, we will talk more about this later.

So what should I do to make CMap work with my ClassX

Well, as I mentioned earlier, CMap is a hash map, a hash map will try to get the "hash value" -- a UINT -- from the key, and use that hash value as the index in the hash table (well, actually it is hash value % hash table size). If more then one key have the same hash value, they will be linked in a linked list. Therefore, the first thing you have to do is to provide a hash function.

CMap will call a templated function HashKey() to do the hashing. The default implementation and specialized version for LPCSTR and LPCWSTR are listed as follows:

// inside <afxtemp.h>

template<class ARG_KEY>
AFX_INLINE UINT AFXAPI HashKey(ARG_KEY key)
{// default identity hash - works for most primitive values
return (DWORD)(((DWORD_PTR)key)>>4);
}// inside <strcore.cpp>

// specialized implementation for LPCWSTR

#if _MSC_VER >= 1100
template<> UINT AFXAPI HashKey<LPCWSTR> (LPCWSTR key)
#else
UINT AFXAPI HashKey(LPCWSTR key)
#endif
{UINT nHash = 0;while (*key)nHash = (nHash<<5) + nHash + *key++;return nHash;
}// specialized implementation for LPCSTR

#if _MSC_VER >= 1100
template<> UINT AFXAPI HashKey<LPCSTR> (LPCSTR key)
#else
UINT AFXAPI HashKey(LPCSTR key)
#endif
{UINT nHash = 0;while (*key)nHash = (nHash<<5) + nHash + *key++;return nHash;
}

As you can see, the default behavior is to "assume" that the key is a pointer, and convert it to DWORD, and that's why you will get "error C2440: 'type cast': cannot convert from 'ClassXXX' to 'DWORD_PTR'" if you don't provide a specialized HashKey() for your ClassX.

And because MFC only has specialized implementations for the LPCSTR and LPCWSTR, and not for CStringA norCStringW, if you want to use CString in CMap, you have to declare CMap<CString, LPCTSTR....>.

OK, now you know how CMap calculates the hash value, but since more than one key may have the same hash value,CMap needs to traverse the whole linked list to find the one with exactly the same key "content", not only with the same "hash value". And when CMap does the matching, it will call CompareElements(), another templated function.

// inside <afxtemp.h>

// noted: when called from CMap,

//        TYPE=KEY, ARG_TYPE=ARG_TYPE

// and note pElement1 is TYPE*, not TYPE

template<class TYPE, class ARG_TYPE>
BOOL AFXAPI CompareElements(const TYPE* pElement1, const ARG_TYPE* pElement2)
{ASSERT(AfxIsValidAddress(pElement1, sizeof(TYPE), FALSE));ASSERT(AfxIsValidAddress(pElement2, sizeof(ARG_TYPE), FALSE));// for CMap<CString, LPCTSTR...>
// we are comparing CString == LPCTSTR
return *pElement1 == *pElement2;
}

Therefore, if you want to use CMap with your own custom ClassX, you will have to provide a specialized implementation for HashKey() and CompareElements().

Example: CMap with CString*

Provided as an example, below is what you need to do to make CMap work with CString*, and of course, using the string content as the key, and not the address of the pointer.

template<> 
UINT AFXAPI HashKey<CString*> (CString* key)
{return (NULL == key) ? 0 : HashKey((LPCTSTR)(*key));
}// I don't know why, but CompareElements can't work with CString*

// have to define this

typedef CString* LPCString;template<>
BOOL AFXAPI CompareElements<LPCString, LPCString> (const LPCString* pElement1, const LPCString* pElement2)
{if ( *pElement1 == *pElement2 ) {// true even if pE1==pE2==NULL
return true;} else if ( NULL != *pElement1 && NULL != *pElement2 ) {// both are not NULL
return **pElement1 == **pElement2;} else {// either one is NULL
return false;}
}

And the main program is as simple as:

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{CMap<CString*, CString*, int, int> map;CString name1 = "Microsoft";CString name2 = "Microsoft";map[&name1] = 100;int x = map[&name2];printf("%s = %d\n", (LPCTSTR)name1, x);*/return 0;
}
--------- console output ---------
Microsoft = 100

Please note that the program can compile without error even without the specialized HashKey() andCompareElements(), but of course, the output will then be 0, probably not what you want.

My final note about CMap

  1. CMap is a hash map and STL::map is a tree map, there is no meaning comparing the two for performance (it would be like comparing apples and oranges!). But if you will retrieve the keys in sorted order, then you will have to use STL::map.
  2. The design of HashKey() is critical to the overall performance. You should provide a HashKey() that has a low collision rate (different keys generally would have different hash values) and is easy to calculate (not a MD5 of the string, etc..). And we have to note that -- at least for some of the classes -- this is not easy.
  3. When using CMap (as well as STL::hash_map), always beware of the hash table size. As quoted in MSDN, "the hash table size should be a prime number. To minimize collisions, the size should be roughly 20 percent larger than the largest anticipated data set". By default, CMap hash table size is 17, which should be okay for around 10 keys. You can change the hash table size with InitHashTable(UINT nHashSize), and can only do so before the first element is added to the map. You can find more prime numbers here. (And don't mix-up with CMap(UINT nBlockSize)nBlockSize is to acquire more than one CAssoc to speed up the creation of a new node.)

这篇关于CMap在用CString做key类型时,ARG_KEY要选LPCTSTR的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/872251

相关文章

shell脚本批量导出redis key-value方式

《shell脚本批量导出rediskey-value方式》为避免keys全量扫描导致Redis卡顿,可先通过dump.rdb备份文件在本地恢复,再使用scan命令渐进导出key-value,通过CN... 目录1 背景2 详细步骤2.1 本地docker启动Redis2.2 shell批量导出脚本3 附录总

Java获取当前时间String类型和Date类型方式

《Java获取当前时间String类型和Date类型方式》:本文主要介绍Java获取当前时间String类型和Date类型方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录Java获取当前时间String和Date类型String类型和Date类型输出结果总结Java获取

SpringBoot改造MCP服务器的详细说明(StreamableHTTP 类型)

《SpringBoot改造MCP服务器的详细说明(StreamableHTTP类型)》本文介绍了SpringBoot如何实现MCPStreamableHTTP服务器,并且使用CherryStudio... 目录SpringBoot改造MCP服务器(StreamableHTTP)1 项目说明2 使用说明2.1

SQL 外键Foreign Key全解析

《SQL外键ForeignKey全解析》外键是数据库表中的一列(或一组列),用于​​建立两个表之间的关联关系​​,外键的值必须匹配另一个表的主键(PrimaryKey)或唯一约束(UniqueCo... 目录1. 什么是外键?​​ ​​​​2. 外键的语法​​​​3. 外键的约束行为​​​​4. 多列外键​

浅谈Redis Key 命名规范文档

《浅谈RedisKey命名规范文档》本文介绍了Redis键名命名规范,包括命名格式、具体规范、数据类型扩展命名、时间敏感型键名、规范总结以及实际应用示例,感兴趣的可以了解一下... 目录1. 命名格式格式模板:示例:2. 具体规范2.1 小写命名2.2 使用冒号分隔层级2.3 标识符命名3. 数据类型扩展命

Redis 热 key 和大 key 问题小结

《Redis热key和大key问题小结》:本文主要介绍Redis热key和大key问题小结,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录一、什么是 Redis 热 key?热 key(Hot Key)定义: 热 key 常见表现:热 key 的风险:二、

redis过期key的删除策略介绍

《redis过期key的删除策略介绍》:本文主要介绍redis过期key的删除策略,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录第一种策略:被动删除第二种策略:定期删除第三种策略:强制删除关于big key的清理UNLINK命令FLUSHALL/FLUSHDB命

MySQL 中查询 VARCHAR 类型 JSON 数据的问题记录

《MySQL中查询VARCHAR类型JSON数据的问题记录》在数据库设计中,有时我们会将JSON数据存储在VARCHAR或TEXT类型字段中,本文将详细介绍如何在MySQL中有效查询存储为V... 目录一、问题背景二、mysql jsON 函数2.1 常用 JSON 函数三、查询示例3.1 基本查询3.2

Pydantic中Optional 和Union类型的使用

《Pydantic中Optional和Union类型的使用》本文主要介绍了Pydantic中Optional和Union类型的使用,这两者在处理可选字段和多类型字段时尤为重要,文中通过示例代码介绍的... 目录简介Optional 类型Union 类型Optional 和 Union 的组合总结简介Pyd

Oracle数据库常见字段类型大全以及超详细解析

《Oracle数据库常见字段类型大全以及超详细解析》在Oracle数据库中查询特定表的字段个数通常需要使用SQL语句来完成,:本文主要介绍Oracle数据库常见字段类型大全以及超详细解析,文中通过... 目录前言一、字符类型(Character)1、CHAR:定长字符数据类型2、VARCHAR2:变长字符数