CString类是vc中一个有关字符串处理的类,其中用到了很多好的技法,如写时复制 技法,内存管理技法等,处理字符串的效率是很高的。 这里仅谈谈copy-on-write技法的实现。 我以vc6中的mfc为例。 或许你曾写过如下的代码: CString str_a = "abcd"; CString str_b = str_a; 当你调试程序时,你可能发现str_a 和 str_b 这两个对象中的唯一的一个data member m_pchDATA是指向同样的一个内存地址,即字符串abcd在堆中的首地址,也即字符串abcd在内存中只有一份copy,并不是两份数据。 当你再次写下如下代码时 str_a = “efg”; 会发现str_a的m_pchDATA指向了堆中的另一块内存efg,并没有将str_b中包含的数据覆盖掉。 可是CString的内存布局只有一个data member(因为它没有继承关系,所以没有虚函数,也就没有虚函数表指针)m_pchData,指向字符串数据,我们也没看见什么引用计数的变量呀。 那么CString是如何做到这一点(copy-on-write)的呢? 原来奥秘就在于有一个叫做CStringData的结构体,定义如下(见afx.h) struct CStringData { long nRefs; // reference count int nDataLength; // length of data (including terminator) int nAllocLength; // length of allocation // TCHAR data[nAllocLength] TCHAR* data() // TCHAR* to managed data { return (TCHAR*)(this+1); } }; 其中第一个成员变量至关重要,可以说它是copy-on-write的核心,它对它所关联的字符串作引用计数,即当前为此值的CString对象(可能在堆栈也可能在堆中)个数,什么叫’关联’呢?让我用一幅图表示一下,你就清楚了, 从图中可以看出m_pchDATA指向的字符串上方实际上是个12字节的CStringData的结构体变量,也就是说CString类通过m_pchDATA这个仅有一个指针就能即访问字符串数据又能访问引用计数nRefs这个CStringData中的变量。那么CString是如何做到这一点的呢? 侯捷先生说过,源码之下,了无秘密。让我们去看看CString的构造函数的源码吧,(见strcore.cpp) CString::CString(LPCTSTR lpsz) { Init(); if (lpsz != NULL && HIWORD(lpsz) == NULL) { UINT nID = LOWORD((DWORD)lpsz); if (!LoadString(nID)) TRACE1("Warning: implicit LoadString(%u) failed\n", nID); } else { int nLen = SafeStrlen(lpsz); if (nLen != 0) { AllocBuffer(nLen); memcpy(m_pchData, lpsz, nLen*sizeof(TCHAR)); } } } 这其中的关键在于AllocBuffer这个函数,下面是它的源码, void CString::AllocBuffer(int nLen) // always allocate one extra character for '\0' termination // assumes [optimistically] that data length will equal allocation length { ASSERT(nLen >= 0); ASSERT(nLen <= INT_MAX-1); // max size (enough room for 1 extra) if (nLen == 0) Init(); else { CStringData* pData; #ifndef _DEBUG //在非调试模式下有4个内存空闲链表链,进行内存池的管理 if (nLen <= 64) { pData = (CStringData*)_afxAlloc64.Alloc(); pData->nAllocLength = 64; } else if (nLen <= 128) { pData = (CStringData*)_afxAlloc128.Alloc(); pData->nAllocLength = 128; } else if (nLen <= 256) { pData = (CStringData*)_afxAlloc256.Alloc(); pData->nAllocLength = 256; } else if (nLen <= 512) { pData = (CStringData*)_afxAlloc512.Alloc(); pData->nAllocLength = 512; } else #endif { pData = (CStringData*) new BYTE[sizeof(CStringData) + (nLen+1)*sizeof(TCHAR)]; pData->nAllocLength = nLen; } pData->nRefs = 1; pData->data()[nLen] = '\0'; pData->nDataLength = nLen; m_pchData = pData->data();//pData虽然消失了,但信息都可以通过m_pchData //来索取了,呵呵 } } 相信这时你应该对CString的copy-on-write机制了然于胸了吧,下面附上CString的copy constructor CString::CString(const CString& stringSrc) { ASSERT(stringSrc.GetData()->nRefs != 0); if (stringSrc.GetData()->nRefs >= 0) { ASSERT(stringSrc.GetData() != _afxDataNil); m_pchData = stringSrc.m_pchData; InterlockedIncrement(&GetData()->nRefs); } else { Init(); *this = stringSrc.m_pchData; } }

评论