【C++智能指针】模拟实现智能指针

前言

C++的智能指针类是为了解决动态创建的对象自动释放的问题。通常与运算符重载和引用计数配合使用。


0x00 CAString类

首先我们自己实现了一个简单的字符串类CAString类:

class CAString
{
public:
    /************************************************************************/
    /* 构造函数                                                              */
    /************************************************************************/
    CAString()
    {
        m_pBuff = new char[1];
        m_pBuff[0] = '\0';
        m_nBuffSize = 1;
        m_nLength = 0;
    }
    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);

    }
    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);
    }
    CAString(CAString&& obj) // 移动构造 移动拷贝
    {
        swap(m_pBuff, obj.m_pBuff);
        swap(m_nLength, obj.m_nLength);
        swap(m_nBuffSize, obj.m_nBuffSize);
    }
    /************************************************************************/
    /* 析构函数                                                              */
    /************************************************************************/
    ~CAString()
    {
        if (m_pBuff != nullptr)
        {
            delete m_pBuff;
            m_pBuff = nullptr;
        }
    }

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

    // 修改字符串的一个字符
    void SetAt(int nIndex, char ch)
    {
        m_pBuff[nIndex] = ch;
    }
public:
    /************************************************************************/
    /* 重载输出运算符                                                        */
    /************************************************************************/
    // 使用全局函数重载 因为要访问到私有数据 因此定义为友元全局函数
    friend ostream& operator<<(ostream& os, const CAString& obj)
    {
        os << obj.m_pBuff;
        return os;
    }
private:
    uint32_t m_nBuffSize;
    uint32_t m_nLength;
    char* m_pBuff;
};

提供了构造、析构函数,重载了输出流运算符,对外部提供了获取字符串地址的接口。

0x01 智能指针1(解决动态创建的对象资源释放的问题)

利用局部对象在出作用域时自动调用析构函数的特点。将对象的指针保存,封装成另一个智能指针类。智能指针类对象在构造函数中保存对象的指针,在出作用域时析构中显式delete保存的对象的指针。

为此我们写一个简单的C++智能指针类:

class CSmartPtr
{
public:
    CSmartPtr(CAString* pObj = nullptr)
    {
        m_pObject = pObj;
    }
    ~CSmartPtr()
    {
        if (m_pObject != nullptr)
        {
            delete m_pObject;
            m_pObject = nullptr;
        }
    }
private:
    CAString* m_pObject;
};

现在我们使用这个智能指针类:

int main()
{ 
    // 局部对象 pObj1和pObj2 出作用域时自动释放对象
    CSmartPtr pObj1(new CAString("string1"));
    CSmartPtr pObj2(new CAString("string2"));

//     cout << pObj1->GetStringBuffer() << endl;
//     cout << *pObj2 << endl;
    return 0;
}

这里我们使用智能指针时非常不方便,没法直接对保存的对象指针进行操作。因为我们将 CAString 类对象的指针保存为 private权限,所以 pObj1pObj2 在外部不能通过访问到保存的 CAString 类对象的指针进而访问 CAString 类对象的成员函数。


下面讨论的是如何通过 CSmartPtr 类对象直接访问到 CAString 类对象的成员函数。

最容易考虑到的方法就是将 CAString 类对象的指针设为 public 权限。但这样通过 CSmartPtr 类对象直接可以修改保存的 CAString 类对象的指针,不建议使用。

还有一种方法,就是在 CSmartPtr 智能指针类中重写所有的 CAString 类的方法,这些方法作为 CSmartPtr 的成员函数,在成员函数中,可以直接对 private权限的 CAString 类指针进行操作。
这种方法不建议使用,首先因为工作量太大,重写所有的 CAString 类的成员函数将使 CSmartPtr 类变得臃肿;另一点就是即使这样写出的 CSmartPtr 类只能管理 CAString 类,不能泛化管理所有的类。


对另一种方法就是通过运算符重载。我们保存的是 CAString 类对象的指针,我们重载指针一些常用的运算符。

class CSmartPtr
{
public:
    CSmartPtr(CAString* pObj = nullptr)
    {
        m_pObject = pObj;
    }
    ~CSmartPtr()
    {
        if (m_pObject != nullptr)
        {
            delete m_pObject;
            m_pObject = nullptr;
        }
    }

    // 为了实现 智能指针 和保存的类的指针一样使用
	// 例如 *、->、&、+等
	// 我们在智能指针类中重载这些运算符
    CAString* operator->() // 类指针可以取得public成员
    {
        return m_pObject;
    }
    CAString** operator&() // 指针可以取地址
    {
        return &m_pObject;
    }
    CAString& operator*() // 指针可以解星号运算
    {
        return *m_pObject;
    }
    CAString* operator+(int n) // 指针可以加整型
    {
        return m_pObject + n;
    }
private:
    CAString* m_pObject;
};

重载了运算符以后,通过 CSmartPtr 类对象直接访问到 CAString 类对象的成员函数,这样使用 CSmartPtr 类对象和使用 CAString 类对象一样。

现在我们再使用这个智能指针类:

int main()
{ 
    // 自动释放对象
    CSmartPtr pObj1(new CAString("string1"));
    CSmartPtr pObj2(new CAString("string2"));

    // CSmartPtr重载了->运算符后返回的是CAString对象的指针
    // 在外部使用起来就像是CAString对象指针一样
    cout << pObj1->GetStringBuffer() << endl;
    
    // CSmartPtr重载了* 运算符后返回的是CAString对象的引用
    // 而CAString重载了输出流运算符 因此可以直接输出
    cout << *pObj2 << endl;

    // 重载&运算符,返回CString对象的地址
    cout << &pObj1 << endl;
    *pObj1;
    pObj1 + 1;

    return 0;
}

智能指针 + 运算符重载 使我们使用智能指针和直接使用类对象的效果一样。而且智能指针解决了类对象指针释放的问题。

0x02 智能指针1完整代码

class CSmartPtr
{
public:
    CSmartPtr(CAString* pObj = nullptr)
    {
        m_pObject = pObj;
    }
    ~CSmartPtr()
    {
        if (m_pObject != nullptr)
        {
            delete m_pObject;
            m_pObject = nullptr;
        }
    }

    // 为了实现 智能指针 和保存的类的指针一样使用
	// 例如 *、->、&、+等
	// 我们在智能指针类中重载这些运算符
    CAString* operator->() // 类指针可以取得public成员
    {
        return m_pObject;
    }
    CAString** operator&() // 指针可以取地址
    {
        return &m_pObject;
    }
    CAString& operator*() // 指针可以解星号运算
    {
        return *m_pObject;
    }
    CAString* operator+(int n) // 指针可以加整型
    {
        return m_pObject + n;
    }
private:
    CAString* m_pObject;
};

int main()
{ 
    // 自动释放对象
    CSmartPtr pObj1(new CAString("string1"));
    CSmartPtr pObj2(new CAString("string2"));

    // CSmartPtr重载了->运算符后返回的是CAString对象的指针
    // 在外部使用起来就像是CAString对象指针一样
    cout << pObj1->GetStringBuffer() << endl;
    
    // CSmartPtr重载了* 运算符后返回的是CAString对象的引用
    // 而CAString重载了输出流运算符 因此可以直接输出
    cout << *pObj2 << endl;

    // 重载&运算符,返回CString对象的地址
    cout << &pObj1 << endl;
    *pObj1;
    pObj1 + 1;

    return 0;
}

0x03 智能指针1存在的问题(等号赋值)

因为我们的 CSmartPtr 类没有重载赋值运算符,也就是没有实现下面这个函数:

CSmartPtr& operator= (const CSmartPtr& obj)
{
	// ...
	return *this;
}

这将导致下面代码中 pObj1 = pObj2; 会发生资源泄露和重释放的问题。

int main()
{ 
    CSmartPtr pObj1(new CAString("string1"));
    CSmartPtr pObj2(new CAString("string2"));

    // 相当于memcpy 把string2的地址赋值给pObj1,此时pObj1和pObj2指向相同的对象
    // 对象出作用域析构,导致重复析构
    pObj1 = pObj2; // 这里会发生资源重复释放的问题。

    return 0;
}

没有重载等号赋值运算符,等号赋值默认就是执行 memcpy 函数。此时 pObj1pObj2 中保存的对象的指针将指向相同的对象。因此在出作用析构是,pObj2 中保存的对象的指针被 delete 了两次,而 pObj1 中保存的对象的指针丢失了,又发生了资源泄露。

0x04 智能指针2(带引用计数的智能指针)

为了解决资源重复释放的问题,我们很自然的想到了使用引用计数(参考:C++引用计数)。

新增一个引用计数指针作为 CSmartPtr 类的成员变量:

uint32_t* m_pRefCount;

提供对引用计数递增和递减的方法:

void AddRefCount()
{
    ++(*m_pRefCount);
}
void SubRefCount()
{
    if (m_pObject != nullptr && m_pRefCount != nullptr)
    {
        --(*m_pRefCount);
        if (*m_pRefCount == 0)
        {
            delete m_pRefCount;
            m_pRefCount = nullptr;
            delete m_pObject;
            m_pObject = nullptr;
        }
    }
}

在构造函数中,初始化引用计数指针,析构函数中递减引用计数。在递减引用计数的函数中,判断如果引用计数为0才会释放保存的对象和引用计数占用的控件。也就是说引用计数和具体的类对象绑定。

构造和析构函数代码:

CSmartPtr()
{
    m_pRefCount = nullptr;
    m_pObject = nullptr;
}
CSmartPtr(CAString* pObj)
{
    m_pRefCount = new uint32_t(0);
    m_pObject = pObj;

    AddRefCount();
}

// 重载=运算符 将原来对象的引用计数减1 新对象的引用计数加1
CSmartPtr& operator=(const CSmartPtr& obj)
{
    SubRefCount();

    m_pObject = obj.m_pObject;
    m_pRefCount = obj.m_pRefCount;
    AddRefCount();
    return *this;
}

~CSmartPtr()
{
    SubRefCount();
}

0x05 智能指针2完整代码

class CSmartPtr
{
public:
    CSmartPtr()
    {
        m_pRefCount = nullptr;
        m_pObject = nullptr;
    }
    CSmartPtr(CAString* pObj)
    {
        m_pRefCount = new uint32_t(0);
        m_pObject = pObj;

        AddRefCount();
    }

    // 重载=运算符 将原来对象的引用计数减1 新对象的引用计数加1
    CSmartPtr& operator=(const CSmartPtr& obj)
    {
        SubRefCount();

        m_pObject = obj.m_pObject;
        m_pRefCount = obj.m_pRefCount;
        AddRefCount();
        return *this;
    }

    ~CSmartPtr()
    {
        SubRefCount();
    }
private:
    /************************************************************************/
    /* 引用计数                                                              */
    /************************************************************************/
    void AddRefCount()
    {
        ++(*m_pRefCount);
    }
    void SubRefCount()
    {
        if (m_pObject != nullptr && m_pRefCount != nullptr)
        {
            --(*m_pRefCount);
            if (*m_pRefCount == 0)
            {
                delete m_pRefCount;
                m_pRefCount = nullptr;
                delete m_pObject;
                m_pObject = nullptr;
            }
        }
    }

public:
    // 为了实现 智能指针和保存的类的指针 一样使用
    CAString* operator->() // 类指针可以取得public成员
    {
        return m_pObject;
    }
    CAString** operator&() // 指针可以取地址
    {
        return &m_pObject;
    }
    CAString& operator*() // 指针可以解星号运算
    {
        return *m_pObject;
    }
    CAString* operator+(int n) // 指针可以加整型
    {
        return m_pObject + n;
    }
private:
    CAString* m_pObject;
    uint32_t* m_pRefCount;
};

int main()
{ 
    CSmartPtr pObj1(new CAString("string1"));
    CSmartPtr pObj2(new CAString("string2"));

    cout << pObj1->GetStringBuffer() << endl;
    cout << *pObj2 << endl;

    pObj1 = pObj2; // 使用引用计数的方法 重载了=运算符

    cout << pObj1->GetStringBuffer() << endl;
    cout << *pObj2 << endl;

    return 0;
}

0x06 模拟auto_ptr

将智能指针1封装成模板就模拟实现了 auto_ptr

template <class TYPE>
class CSmartPtr
{
public:
    CSmartPtr(TYPE* pObj = nullptr)
    {
        m_pObject = pObj;
    }
    ~CSmartPtr()
    {
        if (m_pObject != nullptr)
        {
            delete m_pObject;
            m_pObject = nullptr;
        }
    }

    TYPE* operator->() // 类指针可以取得public成员
    {
        return m_pObject;
    }
    TYPE** operator&() // 指针可以取地址
    {
        return &m_pObject;
    }
    TYPE& operator*() // 指针可以解星号运算
    {
        return *m_pObject;
    }
    TYPE* operator+(int n) // 指针可以加整型
    {
        return m_pObject + n;
    }
private:
    TYPE* m_pObject;
};

使用:

int main()
{ 
    CSmartPtr<CAString> pObj1(new CAString("string1"));
    CSmartPtr<CAString> pObj2(new CAString("string2"));

    cout << pObj1->GetStringBuffer() << endl;
    cout << *pObj2 << endl;
    
    return 0;
}

0x07 模拟share_ptr

将智能指针2封装成模板就模拟实现了 share_ptr

template <class TYPE>
class CSmartPtr
{
public:
    CSmartPtr()
    {
        m_pRefCount = nullptr;
        m_pObject = nullptr;
    }
    CSmartPtr(TYPE* pObj)
    {
        m_pRefCount = new uint32_t(0);
        m_pObject = pObj;

        AddRefCount();
    }

    // 重载=运算符 将原来对象的引用计数减1 新对象的引用计数加1
    CSmartPtr& operator=(const CSmartPtr& obj)
    {
        SubRefCount();

        m_pObject = obj.m_pObject;
        m_pRefCount = obj.m_pRefCount;
        AddRefCount();
        return *this;
    }

    ~CSmartPtr()
    {
        SubRefCount();
    }
private:
    /************************************************************************/
    /* 引用计数                                                              */
    /************************************************************************/
    void AddRefCount()
    {
        ++(*m_pRefCount);
    }
    void SubRefCount()
    {
        if (m_pObject != nullptr && m_pRefCount != nullptr)
        {
            --(*m_pRefCount);
            if (*m_pRefCount == 0)
            {
                delete m_pRefCount;
                m_pRefCount = nullptr;
                delete m_pObject;
                m_pObject = nullptr;
            }
        }
    }

public:
    // 为了实现 智能指针和保存的类的指针 一样使用
    TYPE* operator->() // 类指针可以取得public成员
    {
        return m_pObject;
    }
    TYPE** operator&() // 指针可以取地址
    {
        return &m_pObject;
    }
    TYPE& operator*() // 指针可以解星号运算
    {
        return *m_pObject;
    }
    TYPE* operator+(int n) // 指针可以加整型
    {
        return m_pObject + n;
    }
private:
    TYPE* m_pObject;
    uint32_t* m_pRefCount;
};

使用:

int main()
{ 
    CSmartPtr<CAString> pObj1(new CAString("string1"));
    CSmartPtr<CAString> pObj2(new CAString("string2"));

    cout << pObj1->GetStringBuffer() << endl;
    cout << *pObj2 << endl;


    pObj1 = pObj2; // 使用引用计数的方法 重载了=运算符

    cout << pObj1->GetStringBuffer() << endl;
    cout << *pObj2 << endl;

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

shlyyy

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

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

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

打赏作者

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

抵扣说明:

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

余额充值