- 写时复制一般是用来节省内存的使用,一般的应用场景在于对象的使用大多的读操作而写操作很少,但是对象又涉及到内存的分配而且需要频繁拷贝对象。这时候写时复制就有大用处了。
- MFC中的CString就用到了写时复制,当对一个CString进行拷贝时只是拷贝内部的字符串指针。
- 下面可以看一下CString的拷贝构造函数实现
CSimpleStringT(_In_ const CSimpleStringT& strSrc)
{
CStringData* pSrcData = strSrc.GetData();
CStringData* pNewData = CloneData( pSrcData );
Attach( pNewData );
}
接下来我们看里面的cloneData实现是什么样的
static CStringData* __cdecl CloneData(_Inout_ CStringData* pData)
{
CStringData* pNewData = NULL;
IAtlStringMgr* pNewStringMgr = pData->pStringMgr->Clone();
if( !pData->IsLocked() && (pNewStringMgr == pData->pStringMgr) )
{//这里就是直接引用字符串指针,同时内部引用计数自增
pNewData = pData;
pNewData->AddRef();
}
else
{//这里就是写时复制
pNewData = pNewStringMgr->Allocate( pData->nDataLength, sizeof( XCHAR ) );
if( pNewData == NULL )
{
ThrowMemoryException();
}
pNewData->nDataLength = pData->nDataLength;
CopyChars( PXSTR( pNewData->data() ), pData->nDataLength+1,
PCXSTR( pData->data() ), pData->nDataLength+1 ); // Copy '\0'
}
return( pNewData );
}
- 不仅是MFC的CString,如果你用过Qt的话,你也可以发现Qt的大部分类都是实现了写时复制的机制,一切都在悄悄地进行。
- 下面我这里模仿Qt的隐式共享写了个轻量级的写时复制模板类,可以简单地实现基本的写时复制,主要是实现一个内附引用计数的数据类,再提供一个封装好操作的数据的包装类,在涉及到修改操作的时候进行复制
#include "type.h"
#include "Atomic.h"
class SharedData
{
public:
//ref初始化为0,由调用方设置
SharedData():m_ref(0),m_shareable(true){}
virtual ~SharedData() = 0;
void addRef();
void release();
inline bool isShareable() const;
inline bool isShared() const;
inline void markUnShareable();
private:
DISABLECOPYANDASSIGN(SharedData)
private:
ulong_z m_ref;
bool m_shareable;
};
SharedData::~SharedData() {}
void SharedData::addRef(){ atmoicIncrement(&m_ref);}
void SharedData::release()
{
ulong_z new_ref = atmoicDecrement(&m_ref);
if (new_ref == 0) {
delete this;
}
}
bool SharedData::isShareable() const { return m_shareable; }
bool SharedData::isShared() const { return m_ref > 1;}
void SharedData::markUnShareable() { m_shareable = false; }
//copy-on-write
template<class T>
class SharedDataPtr
{
public:
SharedDataPtr() noexcept: m_p(NULL){}
explicit SharedDataPtr(T *data) noexcept:m_p(data)
{
if(m_p)
m_p->addRef();
}
~SharedDataPtr() noexcept
{
if(m_p)
m_p->release();
}
inline SharedDataPtr(const SharedDataPtr& other) noexcept:m_p(other.m_p)
{
if (!m_p)
return;
if (m_p->isShareable() == false)
{
T* temp = clone();
m_p = temp;
}
m_p->addRef();
}
// copy-swap idiom是异常安全强保证的基础,此处使用值传递传参搭配使用
// 编译器传参有优化
SharedDataPtr& operator=(SharedDataPtr other) noexcept
{
other.swap(*this);
return *this;
}
inline const T* operator->() const { return m_p;}
inline T* operator->() { detach(); return m_p; }
inline const T& operator*() const { return *m_p;}
inline T& operator*() { detach(); return *m_p; }
inline operator T *() const { return m_p; }
inline operator const T *() { detach(); return m_p; }
inline bool operator!() const { return !m_p; }
void detach()
{
if (!m_p)
return;
if (m_p->isShareable() && m_p->isShared())
{
T* temp = clone();
temp->addRef();
m_p->release();
m_p = temp;
}
m_p->markUnShareable();
}
inline void swap(SharedDataPtr& other) noexcept { std::swap(m_p, other.m_p);}
private:
T* clone() { return new T(*m_p); }
private:
mutable T* m_p;
};
namespace std
{
template<class T>
void swap(SharedDataPtr<T>& lhs, SharedDataPtr<T>& rhs) noexcept
{
lhs.swap(rhs);
}
}
使用起来也很简单明了,继承SharedData实现一个数据类(必须实现拷贝构造函数和析构函数), 然后用SharedPtr包装在实际的RealData类(拷贝构造函数、赋值运算函数与析构函数不用手动实现,编译器默认生成即可)中使用
class DataPrivate : public SharedData
{
public:
DataPrivate(const char* sz, int num):m_sz(NULL),m_num(num)
{
if(sz)
{
size_t len = strlen(sz);
m_sz = new char[len + 1];
strcpy_s(m_sz,len+1, sz);
}
std::cout << "DataPrivate()" << std::endl;
}
DataPrivate(const DataPrivate& other):
m_sz(NULL),m_num(other.m_num)
{
if (other.m_sz)
{
size_t len = strlen(other.m_sz);
m_sz = new char[len + 1];
strcpy_s(m_sz, len+1, other.m_sz);
}
std::cout << "DataPrivate(const DataPrivate& other)" << std::endl;
}
~DataPrivate() noexcept
{
if (m_sz)
delete m_sz;
std::cout << "~DataPrivate()" << std::endl;
}
const char* constStr() const noexcept
{
return m_sz;
}
char* str() noexcept
{
return m_sz;
}
int num() const noexcept
{
return m_num;
}
private:
char* m_sz;
int m_num;
};
class MyData
{
public:
MyData(const char* sz, int num): m_p(new DataPrivate(sz, num))
{}
~MyData()
{}
const char* constStr() const noexcept
{
return m_p->constStr();
}
char* str() noexcept
{
return m_p->str();
}
int number() const
{
return m_p->num();
}
private:
SharedDataPtr<DataPrivate> m_p;
};
void testSharedData()
{
{
MyData data1("xxx", 3);
MyData data2(data1);
const char* str1 = data1.constStr();
char* str11 = data1.str();
printf("data1 cosntStr: %p str: %p\n", str1, str11);
const char* str2 = data2.constStr();
char* str21 = data2.str();
printf("data2 cosntStr: %p str: %p\n", str2, str21);
}
}
结果输出:
可以看到调用了str()非常成员函数后,内部字符串指针拷贝了一份,而后面的data在这基础上调用str()成员函数就不会拷贝了,因为只有一个实例就不用拷贝了。而这你仅仅看DataPrivate的实现是看不出来的,具体的细节都由SharedDataPtr给实现了。