Programmers like me, learnt STL::map before CMap always think CMap is difficult to use, and always try to use CMap in the way as a STL::map. In this article, I will explain more information about CMap and what should you do to use it for your own custom class. And as the end of this article, I will show an example of how to use CMap correctly with CString* (note I mean CString pointer and not CString :> )
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) as STL::map. Below shows the internal structure of a CMap.
Many people get confused about CMap's declaration CMap<KEY, ARG_KEY, VALUE, ARG_VALUE>, why not just CMap<KEY, VALUE>?
In fact, the ultimate data container in CMap is CPair, and the internal of CPair is {KEY, VALUE}. Therefore, CMap will 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_KEY seems always be a correct answer, except
You are using primitive date types like int, char, where pass-by-value makes no difference (may be even faster) with pass-by-reference If you use CString as KEY, you should use LPCTSTR as ARG_KEY and not CString&, we will talk more about this laterWell, as I mentioned earlier, CMap is a hash map, a hash map will try to get the "hash value" -- an 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 link in a link 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 >= 1100template<> UINT AFXAPI HashKey<LPCWSTR> (LPCWSTR key)#elseUINT AFXAPI HashKey(LPCWSTR key)#endif{ UINT nHash = 0; while (*key) nHash = (nHash<<5) + nHash + *key++; return nHash;}// specialized implementation for LPCSTR#if _MSC_VER >= 1100template<> UINT AFXAPI HashKey<LPCSTR> (LPCSTR key)#elseUINT 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" 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 have specialized implementation for the LPCSTR and LPCWSTR, and not for CStringA nor CStringW, this is why if you want to use CString in CMap, you have to declare CMap<CString, LPCTSTR....>
Ok, now you know how CMap calculate the hash value, but since more than one key may have the same hash value, CMap needs to traverse the whole link list to find the one with exactly the same key "content", not only with the same "hash value". And when CMap do 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 TYPEtemplate<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 you own custom ClassX, you will have to provide a specialized implementation for HashKey() and CompareElements().
Provided as an example, below is what you need to do to make CMap works 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 thistypedef 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 the following:
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 = 100Please note that the program can compile without error even without the specialized HashKey() and CompareElements(), but of course, the out output will then be 0, probably not what you want.
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=649293