• 十三、智能指针二


    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最大程度地模拟了原生指针的行为

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

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

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

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

  • 相关阅读:
    一些坑点
    [Luogu P4168] [Violet]蒲公英 (分块)
    冬令营颓废笔记
    WC2019 填坑记
    [Luogu P1829] [国家集训队]Crash的数字表格 / JZPTAB (莫比乌斯反演)
    [Luogu P2522] [HAOI2011]Problem b (莫比乌斯反演)
    [Luogu P3327] [SDOI2015]约数个数和 (莫比乌斯反演)
    [Luogu P3455] [POI2007]ZAP-Queries (莫比乌斯反演 )
    [Luogu P2257] YY的GCD (莫比乌斯函数)
    杭电 1166 敌兵布阵 (线段树)
  • 原文地址:https://www.cnblogs.com/chenke1731/p/9655618.html
Copyright © 2020-2023  润新知