IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 【C++智能指针】模拟实现智能指针 -> 正文阅读

[C++知识库]【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;
}
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-03-06 12:43:52  更:2022-03-06 12:44:30 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 5:17:06-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码