十三、智能指针二

1、SmartPointer智能指针重构

需求:使用智能指针SmartPointer替换单链表LinkList中的原生指针

将原生指针更改为智能指针后,解决全部的编译问题,程序还是会出错,问题在于:SmartPointer的设计方案存在的一些特性

  • 指针的生命周期结束时主动释放堆空间
  • 一片堆空间最多只能有一个指针标识
  • 不允许指针运算和指针比较

需求:创建新的指针指针

Pointer是智能指针的抽象父类(模板)

  • 纯虚析构函数virtual ~Pointer() = 0
  • 重载operator ->()
  • 重载operator* ()

智能指针新的设计方案

template <typename T>
class Pointer : public Object
{
protected:
    T* m_pointer;
public:
    Pointer(T* p = NULL)
    {
        m_pointer = p;
    }
    T* operator-> ()
    {
        return m_pointer;
    }
    T& operator* ()
    {
        return *m_pointer;
    }
    bool inNull()
    {
        return (m_pointer == NULL);
    }
    T* get()
    {
        return m_pointer;
    }
    // 只要没有实现一个具体的析构函数,Pointer继承于Object,就是一个抽象类
};

修改SmartPointer,继承于Pointer

#ifndef SMARTPOINTER_H
#define SMARTPOINTER_H

#include "Pointer.h"

namespace DTLib
{

// 构建一个智能指针模板类
template<typename T>
class SmartPointer : public Pointer<T>
{
public:
    // 构造函数,初始化传参为堆空间地址

    SmartPointer(T* p = NULL) : Pointer<T>(p)   // 调用父类的构造函数的形式
    {
        // 构造函数调用父类的构造函数
    }

    // 拷贝构造函数
    SmartPointer(const SmartPointer<T>& obj)
    {
        this->m_pointer = obj.m_pointer;
        const_cast<SmartPointer<T>&>(obj).m_pointer = NULL;
        //
    }

    SmartPointer<T>& operator = (const SmartPointer<T>& obj)
    {
        if(this != &obj)
        {
            // 释放掉原来指向的那个堆空间
            // 如果先删除m_pointer指向的堆空间,就有可能导致异常抛出
            // 要保证异常安全性
            // delete m_pointer;
            // 指向新的堆空间
            // m_pointer = obj.m_pointer;
            // 删除obj对象中m_pointer与这个堆空间的关联,保证一个堆空间只有一个指针指向这个堆空间
            // const_cast<SmartPointer<T>&>(obj).m_pointer = NULL;
           
            // 为了异常安全性,用一个临时变量保存this->pointer指针,方便释放
            T* p = this->m_pointer;
            this->m_pointer = obj.m_pointer;
            const_cast<SmartPointer<T>&>(obj).m_pointer = NULL;
            delete p;
        }
        return *this;
    }

    // 析构函数需要冲重写,否则就还是一个抽象类
    ~SmartPointer()
    {
        delete this->m_pointer;
    }
};

}

#endif // SMARTPOINTER_H

2、SharedPointer智能指针

需求:多个智能指针指向同一片堆空间,并且这些指针支持自动释放

SharedPointer设计要点:类模板

  • 通过计数机制ref标识堆内存
  • 堆内存被指向时:ref++
  • 指针被置空时:ref--
  • ref == 0时:释放堆内存

计数机制原理剖析:

3个指针同时指向了堆空间中的统一对象,与对象相关联的计数标识应该是3。如果将shared_pointer_3置空,应该计数减一,计数变量为2;如果全部置空,计数变量为0,意味着最后一个智能指针要将堆空间里面的对象销毁掉,将堆空间的内存释放。

虚线矩形框将对象和计数变量框在了一起,意味着每一个堆空间中的对象都和这个计数变量相关联,这个计数变量也位于堆空间里面。在具体实现上计数变量也是在堆空间里面创建的,并且计数变量的生命周期和这个对象的生命周期是完全一致的。

SharedPointer类的声明

template <typename T>
class SharedPointer : public Pointer<T>
{
protected:
    int* m_ref; // 计数机制成员指针
    // 成员指针指向堆空间里面创建的计数变量
public:
    SharedPointer(T* p = NULL);
    SharedPointer(const SharedPointer<T>& obj);
    
    SharedPointer<T>& operator = (const SharedPointer<T>& obj);

    void clear();   // 当前指针置空

    ~SharedPointer();
    
};

由于SharedPointer支持多个对象同时指向一片堆空间,因此必须支持比较操作,使智能指针最大限度上接近原生指针的逻辑。

具体实现如下:

// SharedPointer.h
#ifndef SHAREDPOINTER_H
#define SHAREDPOINTER_H

#include <cstdlib>
#include "Exception.h"
#include "Pointer.h"

namespace DTLib
{
template <typename T>
class SharedPointer : public Pointer<T>
{
protected:
    int* m_ref; // 计数机制成员指针

    // 进行函数封装
    void assign(const SharedPointer<T>& obj)
    {
        this->m_ref = obj.m_ref;            // 将当前指针对象的ref成员指针指向了对应的计数对象
        this->m_pointer = obj.m_pointer;    // m_pointer指向对应的堆内存
        // 还不够,注意计数机制,计数变量需要+1
        if (this->m_ref)
        {// 计数变量合法
            (*this->m_ref)++;
        }
    }

public:
    // 首先是构造函数对成员变量初始化
    SharedPointer(T* p = NULL) : m_ref(NULL)    // 将指向计数变量的成员指针,初始化为空
    {
        if (p)
        {
            // 首先在堆空间中创建一个计数变量
            // 在堆空间中申请4个字节空间作为存放计数变量的内存空间
            this->m_ref = static_cast<int*>(std::malloc(sizeof(int)));  // malloc返回类型是void*
            // 判断申请是否成功
            if (this->m_ref)
            {
                *(this->m_ref) = 1;     // 意味着参数指针p指向的堆空间已经有了一个SharedPointer智能指针对象来指向了
                this->m_pointer = p;    // 将成员指针变量指向参数p对应的堆空间
            }
            else
            {// malloc不成功,意味着内存不够用,抛异常
                THROW_EXCEPTION(NoEnoughMemoryException, "No memory to creat SharedPointer object...");
            }
        }
    }

    SharedPointer(const SharedPointer<T>& obj)
    {
        //this->m_ref = obj.m_ref;          // 将当前指针对象的ref成员指针指向了对应的计数对象
        //this->m_pointer = obj.m_pointer;  // m_pointer指向对应的堆内存
        //// 还不够,注意计数机制,计数变量需要+1
        //if (this->m_ref)
        //{// 计数变量合法
        //  (*this->m_ref)++;
        //}
        assign(obj);
    }

    // 赋值操作符重载函数
    SharedPointer<T>& operator = (const SharedPointer<T>& obj)
    {
        if (this != &obj)
        {// 避免自赋值
            // 逻辑与拷贝构造类似,但是需要做清空操作
            // 当前的SharedPointer对象已经指向了另一片堆空间了,在做赋值操作前,应该将当前的智能指针对象置空,不再指向任何堆空间
            // 在赋值之前,置空 clear()
            clear();

            //// 可以代码复用,封装内部函数
            //this->m_ref = obj.m_ref;          
            //this->m_pointer = obj.m_pointer;          
            //if (this->m_ref)
            //{
            //  (*this->m_ref)++;
            //}
            assign(obj);
        }

        return *this;
    }

    void clear()   // 当前指针置空
    {
        T* toDel = this->m_pointer;
        int* ref = this-> m_ref;

        this->m_pointer = NULL;
        this->m_ref = NULL;

        if (ref)
        {// 当前计数变量合法
            (*ref)--;
            if (*ref == 0)
            {// 为0标识该堆空间已经没有任何智能指针对象去指向了,应该释放该堆空间
                free(ref);      // 释放计数变量
                delete toDel;   // 释放堆空间
            }
        }
    }

    ~SharedPointer()
    {
        clear();
    }

};

}

#endif // SHAREDPOINTER_H

测试:

int main()
{
    SharedPointer<Test> sp0 = new Test();
    SharedPointer<Test> sp1 = sp0;
    
    SharedPointer<Test> sp2 = NULL;

    sp2 = sp1;
    sp2->value = 100;
    cout << sp0->value << endl;
    cout << sp1->value << endl;
    cout << sp2->value << endl;

    cout << (sp0 == sp2) << endl;

    return 0;
}

结果:

Test()
100
100
100
0
~Test()

sp0 sp1 sp2均指向了同一片堆空间,通过sp2->value更改值之后,和原生指针效果一样,但是进行指针比较的时候,需要重载比较操作符

#ifndef SHAREDPOINTER_H
#define SHAREDPOINTER_H

#include <cstdlib>
#include "Exception.h"
#include "Pointer.h"


namespace DTLib
{
template <typename T>
class SharedPointer : public Pointer<T>
{
...
};

// 在全局区重载比较操作符
template <typename T>
bool operator == (const SharedPointer<T>& l, const SharedPointer<T>& r)
{
    return (l.get() == r.get());
    // get()函数不是const成员函数,所以不能被const对象调用
}

template <typename T>
bool operator != (const SharedPointer<T>& l, const SharedPointer<T>& r)
{
    return !(l == r);   // !=操作符重载的实现用上面==操作的实现就可以了
}

}
#endif // SHAREDPOINTER_H

智能指针的使用规则:

  • 只能用来指向堆空间中的某个变量(对象)
  • 不同类型的智能指针对象不能混合使用
  • 不用使用delete释放智能指针指向的堆空间

3、小结

SharedPointer最大程度地模拟了原生指针的行为

计数机制确保多个智能指针合法地指向同一片堆空间

智能指针只能用于指向堆空间中的内存

不同类型的智能指针不要混合使用

堆对象的生命周期由智能指针进行管理