引言:在C++中,堆内存的管理都是由在栈上开辟的变量名间接控制的。这是因为在堆上开辟的空间是没有名字的,我们无法直接对齐进行控制。所以当释放该堆内存时,也需要我们去人为的释放它。
智能指针的出现就是为了解决上面的问题,当释放一个堆内存时,它将由系统释放,而不是我们人为的释放它。这样就会避免许多内存泄漏的产生。
只能指针总共分为4种:
C++98 auto_ptr
C++11 unique_ptr shared_ptr weak_ptr
auto_ptr
特点:
1.所有权唯一(即一个堆内存只能有一个指针指向,绝对占有)
2.新智能指针获取到所有权时,取消掉旧智能指针的所有权
缺点:
所有权的权限转移,会使得对象提前失效。
完整代码如下:
#include <iostream>
#include <memory>
template <typename T>
class auto_ptr {
public:
auto_ptr(T* ptr)
:mptr(ptr)
{}
auto_ptr(auto_ptr<T>& rhs)//浅拷贝构造函数
{
mptr = rhs.Release();//让新的指针指向内存块,同时让旧的指针指向为空
/*
T* ptr = rhs.mptr;
mptr = NULL;
mptr=ptr;
*/
}
auto_ptr<T>& operator=(auto_ptr<T>& rhs)//赋值运算符重载函数
{
if (this != &rhs)//判断是否自赋值
{
delete mptr;
mptr = rhs.Release();
}
return *this;
}
~auto_ptr()
{
delete mptr;
}
T* operator->()//->运算符重载
{
return mptr;
}
T& operator*()//*运算符重载
{
return *mptr;
}
private:
T* Release()
{
T* ptr = mptr;
mptr = NULL;
return ptr;
}
T* mptr;
};
int main()
{
auto_ptr<int> p1 = new int;
auto_ptr<int> p2 = p1;//此时p1的唯一权已经转移到p2中,此时p1已经失效
std::cout << "the p1's size is:" << sizeof(p1) << std::endl;
std::cout << "the p3's size is:" << sizeof(p2) << std::endl;
return 0;
}
带标志位的智能指针
为了解决上面auto_ptr让指针提前失效的方法,又出现了一种带标志位的指针。它取消了堆内存的唯一权,即一块内存可以有多个指针指向。但是它的释放权此时唯一,也就是说当最后一个指针(拥有释放权的指针一旦释放)执行析构函数,那么指向这块空间的所有指针也将会释放。
基本概念:新智能指针获取到释放权后,取消掉旧智能指针的释放权
代码如下:
#include <iostream>
#include <memory>
using std:: ostream;
template <typename T>
class smartptr
{
public:
smartptr(T* ptr)
:mptr(ptr)
{}
smartptr(smartptr<T>& rhs)
{
mptr = rhs.mptr;
flag = rhs.flag;
rhs.flag = false;
}
smartptr<T>& operator=(smartptr<T>& rhs)
{
if (this != &rhs)
{
if (flag)
{
delete mptr;
}
mptr = rhs.mptr;
flag = rhs.flag;
rhs.flag = false;
}
return *this;
}
~smartptr()
{
if (flag)
{
delete mptr;
}
mptr = NULL;
}
T& operator*()
{
return *mptr;
}
T* operator->()
{
return mptr;
}
private:
template <typename T>
friend ostream& operator<<(ostream& os, smartptr<T>& rhs);
T* mptr;
bool flag;
};
template <typename T>
ostream& operator<<(ostream& os, smartptr<T> &rhs)
{
os << rhs.mptr;
os << rhs.flag;
return os;
}
int main()
{
smartptr<int> p1 = new int;
smartptr<int> p2 = p1;
std::cout << p1 << std::endl;
return 0;
}
unique_ptr
这个智能指针将拷贝构造函数和赋值运算符重载函数写在了私有下,相当于禁止了拷贝和赋值的操作,也被成为最简单的智能指针。
shared_ptr
shared_ptr也叫强智能指针,它依赖一个含有引用计数的结构体Node。
结构体Node设计思想:在Node中存放两个变量void* addr和,int ref.其中addr用来存放在堆内存中开辟出来的地址,(因为开辟堆内存的类型不同,所以将它转为一个void* 的内存)
每当有堆内存开辟出来,我们就将内存的地址存放进来,同时将引用计数++。每当调用一次析构函数,就将堆内存所对应引用计数的值–,如果此时引用计数的值为0,那么我们就认为此时没有指针在指向该内存块,此时将内存块销毁以供系统重新分配。
我们可以将每一个Node看作是结构体中的一条数据,而这个结构体中存放着若干条数据用来存放开辟的堆内存的地址。所以我们会将结构体Node放在一个类中,这个类将提供一系列的方法来添加、查找和删除Node。
#include <iostream>
class mem_ref//结构体方法的接口
{
public:
mem_ref()//默认的构造函数
{
size = 0;//将下标置为0
}
void addref(void* ptr)//添加一个新节点
{
if (ptr == NULL)//如果指针为空(指向的是空)
{
return;//不做处理,直接return出去
}
int index = find(ptr);//定义一个整形值来结构find的返回值
if (index < 0)//如果返回值小于0,说明没有找到这个节点,这时需要为结构添加一个新节点
{
node[size].addr = ptr;//添加新堆内存地址到新节点中
node[size].ref = 1;//将新节点的引用计数初始化为1
size++;//当前下标已经有了地址,将下标往下走一个
}
else//如果大于0,说明找到了指针指向所对应的地址
{
node[index].ref++;//将引用计数++(此时又多了一个指针指向该堆内存)
}
}
void deleteref(void* ptr)//删除一个指向
{
if (ptr == NULL)//如果指针为空(指向的是空)
{
return;//不做处理,直接return出去
}
int index = find(ptr);//定义一个整形值来结构find的返回值
if (index < 0)//如果没有找到指针对应的地址,说明在结构体中没有存储该指针的地址
{
throw std::exception("ptr is error!");//发出异常
}
else
{
if (node[index].ref > 0)//返回值大于0表明找到了该内存的位置
{
node[index].ref--;//将引用计数--
}
}
}
int getref(void* ptr)//查找一个地址,并返回指向该地址的个数
{
if (ptr == NULL)//指向为空
{
return -1;//返回-1
}
int index = find(ptr);//定义一个整形值来结构find的返回值
if (index < 0)//返回值小于0,没有找到
{
return -1;//报错
}
else
{
return node[index].ref;//返回相应的个数
}
}
private:
int find(void* ptr)
{
for (int i = 0; i < size; i++)//i小于当前存数个数的下标
{
if (node[i].addr = ptr)遍历下标中的堆内存,查看是否相匹配
{
return i;
}
}
return -1;
}
struct Node
{
Node(void* add=NULL, int cnt = 0)//Node的构造函数
:addr(add), ref(cnt)
{}
void* addr;//定义一个void*的指针来指向该内存(不能保证堆内存的类型,所以使用void*)
int ref;//引用计数
};
Node node[10];//申请10个空间存放堆内存和引用个数
int size;
};
template <typename T>
class shared_ptr
{
public:
shared_ptr(T* ptr = NULL)
:mptr(ptr)//构造函数初始化
{
mm.addref(mptr);//每初始化一个就调用一次addref()接口
}
shared_ptr(shared_ptr<T>& rhs)//拷贝构造函数
{
mptr = rhs.mptr;//将mptr指向rhs.mptr
mm.addref(mptr);//每次指向一个堆内存,该堆内存的引用计数++
}
shared_ptr<T>& operator=(shared_ptr<T>& rhs)//赋值运算符的重载函数
{
if (this != &rhs)//判断是否自赋值
{
mm.deleteref(mptr);//将赋值的引用计数--
if (mm.getref(mptr) == 0)//如果--后引用计数为0,则删除该堆内存
{
delete mptr;
}
mptr = rhs.mptr;//rhs.mptr指向mptr
mm.addref(mptr);//赋值后,多了一个指向来指向该堆内存,调动该addref()函数
}
return *this;//如果是自赋值,直接将this指针return出去
}
~shared_ptr()
{
mm.deleteref(mptr);//调动deleteref删除一个引用计数
if (mm.getref(mptr) == 0)//判断是否为0
{
delete mptr;//如果是,删除该堆内存
}
else
{
mptr = NULL;//如果不是,说明还有其他的指针指向该堆内存,将该指针的指向置为空
}
}
T* operator->()
{
return mptr;
}
T& operator*()
{
return *mptr;
}
private:
T* mptr;
static mem_ref mm;//定义一个全局变量来调动接口
};
template <typename T>
mem_ref shared_ptr<T>::mm;//mem_ref类型的,在shared_ref作用于下的一个全局静态变量
int main()
{
int* p = new int;
shared_ptr<int> sp1(p);
shared_ptr<int> sp2(p);
shared_ptr<int> sp3(p);
return 0;
}
但是shared_ptr智能指针也会存在一些缺陷。当两个对象相互引用时,就会将各自的引用计数都++一下,导致在释放指针的时候,会出现一个指向但是又两个引用计数的局面。
class B;
class A
{
public:
A()
{
std::cout << "A::A()" << std::endl;
}
~A()
{
std::cout << "A::~A()" << std::endl;
}
public:
Shared_Ptr<B> spa;
};
class B
{
public:
B()
{
std::cout << "B::B()" << std::endl;
}
~B()
{
std::cout << "B::~B()" << std::endl;
}
public:
Shared_Ptr<A> spb;
};
int main()
{
Shared_Ptr<A> pa(new A());
Shared_Ptr<B> pb(new B());
pa->spa = pb;
pb->spb = pa;
return 0;
}
假设以上这种情况,在A类中定义了一个B类型的变量spa,又在B类中定义了一个A类型的变量spb。在new(A)中,会出现一个B类型的对象spa,此时也会调动shared_ptr的构造函数调动,而对象pa又指向了对象spa,同理new(B)也是一样:
为了解决这一问题,人们又设计出了weak_ptr(弱智能指针),这个智能指针专门用于解决强智能指针相互引用的问题
1.不添加引用计数
2.结合强智能指针一起使用,不能单独使用