十三、智能指针二
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
最大程度地模拟了原生指针的行为计数机制确保多个智能指针合法地指向同一片堆空间
智能指针只能用于指向堆空间中的内存
不同类型的智能指针不要混合使用
堆对象的生命周期由智能指针进行管理