十三、智能指针二

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

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

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

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

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

posted @ 2018-09-16 11:19  小胖鼠  阅读(329)  评论(0编辑  收藏  举报