C++写拷贝

前言

Write On Copy
需要先看一下 C++引用计数 的内容


0x00 为什么会有写拷贝

因为浅拷贝会重复释放资源,因此C++用深拷贝来解决浅拷贝的这个问题。

因为深拷贝会带来资源占用空间太大的问题,因此C++引入了引用计数的方法解决了深拷贝的这个问题,也顺便解决了浅拷贝重复释放资源的问题。

引用计数虽然好用,但资源毕竟只有一份,如果一个人修改了资源,则其他人访问到的资源都是修改以后的资源。

为了解决引用计数遗留的问题,C++又引入了写拷贝的方法,在引用计数的基础上,增加写时拷贝的功能,当资源被修改时,会先拷贝一个资源的副本,然后对副本进行修改,这样其他的人访问的资源依旧是原来的资源,就不会有因资源修改而导致其它对象访问到修改后的资源的问题。

0x01 写拷贝的实现

在所有修改资源的地方,增加写时拷贝。如果没有修改资源,则不需要进行写拷贝。

需要注意的是,只有资源的引用计数大于1时,才应该进行写拷贝。也就是说引用计数为1只有一个人使用,没必要进行写拷贝。

下面的代码是在 C++引用计数 的基础上进行修改的。

我们把写拷贝封装成私有成员函数:

void WriteOnCopy()
{
    // 引用计数大于1时才会进行写拷贝
    if (GetRefCnt() > 1)
    {
        // 原来的引用计数减1
        SubRefCnt();

        // 拷贝原来的数据
        char* pNewBuf = new char[m_nBuffSize];
        strcpy_s(pNewBuf, m_nBuffSize, m_pBuff);
        m_pBuff = pNewBuf;
		
		// 新的引用计数
        uint32_t* pNewRefCount = new uint32_t(0); 
        m_pRefCnt = pNewRefCount;
        // 新的引用计数加1
        AddRefCnt();
        
        cout << "Write On Copy" << endl;
    }
}

在有对资源修改的地方我们调用写拷贝函数:

// 修改字符串的一个字符
void SetAt(int nIndex, char ch)
{
    WriteOnCopy(); // 修改资源时 先调用写拷贝

    m_pBuff[nIndex] = ch;
}

完整代码:

#include <iostream>
#include <stdint.h>  // uint32_t
using namespace std;

class CAString
{
public:
    /************************************************************************/
    /* 构造函数                                                              */
    /************************************************************************/
    CAString()
    {
        m_pBuff = new char[1];
        m_pBuff[0] = '\0';
        m_nBuffSize = 1;
        m_nLength = 0;

        // 申请引用计数的空间并初始化为0
        m_pRefCnt = new uint32_t(0);

        // 构造函数中引用计数加1
        AddRefCnt();
    }
    CAString(const char* str)
    {
        m_nLength = strlen(str);
        m_nBuffSize = m_nLength + 1;
        m_pBuff = new char[m_nBuffSize];
        strcpy_s(m_pBuff, m_nBuffSize, str);

        // 申请引用计数的空间并初始化为0
        m_pRefCnt = new uint32_t(0);

        // 构造函数中引用计数加1
        AddRefCnt();
    }
    CAString(const CAString& obj) // 拷贝构造
    {
        // 深拷贝
        /*
        m_nLength = obj.m_nLength;
        m_nBuffSize = obj.m_nBuffSize;
        m_pBuff = new char[m_nBuffSize];
        strcpy_s(m_pBuff, m_nBuffSize, obj.m_pBuff);
        */

        // 浅拷贝
        m_pBuff = obj.m_pBuff;
        m_nLength = obj.m_nLength;
        m_nBuffSize = obj.m_nBuffSize;

        // 拷贝 引用计数的指针
        m_pRefCnt = obj.m_pRefCnt;


        // 构造函数中引用计数加1
        AddRefCnt();
    }
    CAString(CAString&& obj) // 移动构造 移动拷贝
    {
        swap(m_pBuff, obj.m_pBuff);
        swap(m_nLength, obj.m_nLength);
        swap(m_nBuffSize, obj.m_nBuffSize);
        swap(m_pRefCnt, obj.m_pRefCnt);
    }
    /************************************************************************/
    /* 析构函数                                                              */
    /************************************************************************/
    ~CAString()
    {
        // 析构函数中引用计数减1
        SubRefCnt();

        // 如果引用计数为0,就释放资源
        if (GetRefCnt() == 0)
        {
            if (m_pBuff != nullptr)
            {
                // 释放字符串资源
                delete m_pBuff;
                m_pBuff = nullptr;
            }
            if (m_pRefCnt != nullptr)
            {
                // 释放引用计数的资源
                delete m_pRefCnt;
                m_pRefCnt = nullptr;
            }
        }
    }

    /************************************************************************/
    /* 外部使用的获取字符串的接口                                           */
    /************************************************************************/
    const char* GetStringBuffer()
    {
        return m_pBuff;
    }


    // 修改字符串的一个字符
    void SetAt(int nIndex, char ch)
    {
        WriteOnCopy(); // 修改资源时 先调用写拷贝

        m_pBuff[nIndex] = ch;
    }

public:
    /************************************************************************/
    /* 重载输出运算符                                                        */
    /************************************************************************/
    // 使用全局函数重载 因为要访问到私有数据 因此定义为友元全局函数
    friend ostream& operator<<(ostream& os, const CAString& obj)
    {
        os << obj.m_pBuff;
        return os;
    }
private:
    void WriteOnCopy()
    {
        // 引用计数大于1时才会进行写拷贝
        if (GetRefCnt() > 1)
        {
            // 原来的引用计数减1
            SubRefCnt();

            // 拷贝原来的数据
            char* pNewBuf = new char[m_nBuffSize];
            strcpy_s(pNewBuf, m_nBuffSize, m_pBuff);
            m_pBuff = pNewBuf;

            uint32_t* pNewRefCount = new uint32_t(0); // 新的引用计数
            m_pRefCnt = pNewRefCount;
            
            
            cout << "Write On Copy" << endl;

            // 新的引用计数加1
            AddRefCnt();
        }
    }
private:
    /************************************************************************/
    /* 对引用计数操作的方法                                                   */
    /************************************************************************/
    uint32_t GetRefCnt()
    {
        return *m_pRefCnt;
    }
    void AddRefCnt()
    {
        *m_pRefCnt++;
    }
    void SubRefCnt()
    {
        *m_pRefCnt--;
    }
private:
    uint32_t m_nBuffSize;
    uint32_t m_nLength;
    char* m_pBuff;

    // 引用计数指针
    uint32_t* m_pRefCnt;
};

int main()
{   
    CAString str1("str1");
    CAString str2("str2");
    CAString str3(str2);

    str3.SetAt(0, 'a'); 

    cout << "str1:" << str1 << endl;
    cout << "str2:" << str2 << endl;
    cout << "str3:" << str3 << endl;

    return 0;
}

0x02 写拷贝需要注意的问题

写拷贝是在修改资源时才会发生。

// 修改字符串的一个字符
void SetAt(int nIndex, char ch)
{
    WriteOnCopy(); // 修改资源时 先调用写拷贝

    m_pBuff[nIndex] = ch;
}

有些函数可能根据条件修改,如果条件都不满足,就不需要修改。并且一个函数应该最多只写拷贝一次。(在函数实现上,加上一个标志位,并且在真正修改资源的地方才进行写拷贝。

比如下面小写转换为大写的成员函数:

void ToUpperCase()
{
    bool bIsUpdated = false;
    for (int i = 0; i < m_nLength; i++)
    {
        if (m_pBuff[i] >= 'a' && m_pBuff[i] <= 'z')
        {
            // 真正要修改时才进行写拷贝 且写拷贝一次
            if (!bIsUpdated)
            {
                WriteOnCopy();
                bIsUpdated = true;
            }
            m_pBuff[i] -= 32;
        }
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shlyyy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值