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是如何做到这一点的呢?
{
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;
}
}
评论