当我们在C++申请内存的时候,使用下面的语句:
int *p = new int;
这行代码的意思就是在堆上申请了一块内存,并用指针p去指向他:
由此,引出来堆和栈的区别:
- 栈:系统开辟、系统释放
- 堆:人为开辟、人为释放
在这里,就存在了人为的问题,如果因为忘记或者是代码跳跃的原因跳过了delete,这个时候就会发生内存泄漏。
如果设计出一种人为开辟,系统释放的机制,就可以解决内存泄漏的问题,即自主的内存回收机制–智能指针
在JAVA中,只有new关键字,没有delete关键字,因为在JAVA中,实现了自主的内存回收机制–垃圾回收机制:在后台开启了一个守护进程,目的是监控变量的生存周期。
智能指针的实现:
- 以前一块内存申请出来是交给一个栈上的变量来管理,现在智能指针是交给栈上的一个对象来管理,那么在对象里面,就要存在一个指针指向堆内存。等到对象的周期到了之后,就会自动的释放里面指针指向的堆内存。
- 因为不知道要用智能指针管理怎样的堆内存,所以应该设计成模板
在C++中,智能指针有四种:
C++98:auto_ptr
C++11:unique_ptr、shared_ptr、weak_ptr
一、auto_ptr
特性:所有权唯一,即指向一块内存的指针数目唯一
- 当我们使用:
Auto_ptr<int> ap1(new int);
Auto_ptr<int> ap2 = ap1;
图解如下:
在释放的时候,ap2对象先释放,把指向的空间和自身的对象都释放了;等到ap1释放的释放,释放了同一块内存,系统就会崩溃。
解决方法:新的智能指针获取该堆内所有权时,就会取消掉旧智能指针的所有权
- 第一种情况类似于浅拷贝,还有一种情况是=运算符的重载
执行后的结果是:
此时,ap1就会有内存泄漏产生,所以也要写一个=运算符的重载。 - 并且还要有*和->的重载,如果对指针进行操作的话,解引用和指向运算符都是有意义的,所以对于对象也要有
实现:
template<typename T>
class Auto_ptr
{
public:
Auto_ptr(T* ptr):
mptr(ptr)
{}
Auto_ptr(Auto_ptr<T>& rhs)
{
mptr = rhs.Release();
}
Auto_ptr<T>& operator=(Auto_ptr<T>& rhs)
{
if(this != &rhs)
{
delete 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 *p = new int;
int *q = p;
*p = 20;
这样写是没有问题的,但是如果是智能指针呢??
- 智能指针
Auto_ptr<int> ap1 = new int;
Auto_ptr<int> ap2 = ap1;
*ap2 = 20;
*ap1 = 10;
会在第四行出现错误,运行前两行代码后:
ap2是有空间指向的,但是对于ap1来说,就会造成系统崩溃。
二、带有标志位的智能指针(不属于C++标准里面的)
- 因为auto_ptr不能共享,所以此智能指针解决的正是共享的问题,即允许多个智能指针指向同一堆内存。
- 但是,在多个指针中只有一个是有内存的释放权限,设置为布尔类型。
权限的转移也很简单:
- 无论旧指针是否有释放权限,都将其权限赋给新指针,并且自己的权限置为false
template<typename T>
class Smart_ptr
{
public:
Smart_ptr(T* ptr)
:mptr(ptr)
{}
Smart_ptr(const Smart_ptr<T>& rhs)
{
mptr = rhs.mptr;
flag = rhs.flag;
rhs.flag = false;
}
Smart_ptr<T>& operator=(const Smart_ptr<T>& rhs)
{
if(this == &rhs)
{
if(flag)
{
delete mptr;
}
mptr = rhs.mptr;
flag = rhs.flag;
rhs.flag = false;
}
return *this;
}
~Smart_ptr()
{
if(flag)
{
delete mptr;
}
mptr = NULL;
}
T& operator*()
{
return *mptr;
}
T& operator->()
{
return mptr;
}
private:
T* mptr;
bool flag;
};
此时,auto_ptr的问题就不存在了:
Auto_ptr<int> ap1 = new int;
Auto_ptr<int> ap2 = ap1;
*ap2 = 20;
*ap1 = 10;
但是,以下这种情况就会出现问题:
void GetSmart_Ptr(Smart_ptr<int> sp)
{}
int main()
{
Smart_ptr<int> sp1(new int);
Smart_ptr<int> sp2 = sp1;
GetSmart_Ptr(sp2);
*sp1 = 20;
return 0;
}
- 首先sp1的flag = true;
- sp1的flag= false;sp2的flag = true;
- 函数形参中的变量的flag = true;等到函数退出后,所有的对象的flag都是false了,对空间都没有释放权限了。就会造成内存泄漏。
三、unique_ptr
在前两种智能指针中,都会存在智能指针提前失效的问题,关键点就在于发生了拷贝或者赋值导致的权限转移,从而提前失效。
禁止权限转移的智能指针–unique_ptr
template<typename T>
class Unique_ptr
{
public:
Unique_ptr(T* ptr)
:mptr(ptr)
{}
~Unique_ptr()
{
delete mptr;
}
T& operator*()
{
return *mptr;
}
T& operator->()
{
return mptr;
}
private:
Unique_ptr(const Unique_ptr<T>&);
Unique_ptr<T>& operator=(const Unique_ptr<T>&);
T* mptr;
};
- 虽然设计思想是所有权唯一,即一个智能指针指向一块区域;但是没有办法控制外部人员的使用行为:
int *p = new int;
Unique_ptr<int> sp1(p);
Unique_ptr<int> sp2(p);
Unique_ptr<int> sp3(p);
如果运行上述代码,会发生图示情况,所以在释放的时候就会发生错误。
由此,提出来shared_ptr智能指针
四、shared_ptr(带引用计数的智能指针)
首先要满足一下两个条件:
- 共享
- 最后一个智能指针对象销毁
怎么判断是否是最后一个对象销毁?
计数器 记录该堆内存有多少个对象指向
计数器的数据结构:
- 如果再有指针指向同一块内存,引用计数+1就好
- 如果引用计数减为0,那么这个指针就要负责对该内存进行释放
class Mem_Ref
{
public:
Mem_Ref()
{
size = 0;
}
void addRef(void* ptr)
{
if(NULL == ptr)
{
return;
}
int index = find(ptr);
if(index < 0)
{
Node tmp(ptr,1);
node[size++] = tmp;
}
else
{
node[index].ref++;
}
}
void delRef(void* ptr)
{
if(NULL == ptr)
{
return;
}
int index = find(ptr);
if(index < 0)//找不到
{
throw std::exception("ptr is error!");
}
else
{
if(node[index].ref > 0)
{
node[index].ref--;
}
}
}
int getRef(void* ptr)
{
if(NULL == ptr)
{
return -1;
}
int index = find(ptr);
if(index < 0)
return -1;
else
{
return node[index].ref;
}
}
private:
int find(void* ptr)
{
for(int i = 0;i < size;i++)
{
if(node[i].addr == ptr)
{
return i;
}
}
return -1;
}
struct Node
{
Node(void* add = NULL,int count = 0)
{
addr = add;
ref = count;
}
void* addr;
int ref;
};
Node node[10];
int size;
};
template<typename T>
class Shared_Ptr
{
public:
Shared_Ptr(T* ptr = NULL)
:mptr(ptr)
{
mr.addRef(mptr);
}
Shared_Ptr(Shared_Ptr<T>& rhs)
{
mptr = rhs.mptr;
mr.addRef(mptr);
}
Shared_Ptr<T>& operator=(Shared_Ptr<T>& rhs)
{
if(this != &rhs)
{
mr.delRef(mptr);
if(mr.getRef(mptr) == 0)
{
delete mptr;
}
mptr = rhs.mptr;
mr.addRef(mptr);
}
return *this;
}
~Shared_Ptr()
{
mr.delRef(mptr);
if(mr.getRef(mptr) == 0)
{
delete mptr;
}
mptr = NULL;
}
T& operator*()
{
return *mptr;
}
T* operator->()
{
return mptr;
}
T* getPtr()
{
return mptr;
}
private:
T* mptr;
static Mem_Ref mr;
};
template<typename T>
Mem_Ref Shared_Ptr<T>::mr;
- 但是当我们对类互相引用的时候就会出现错误:
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;
}
会造成内存泄漏。
Shared_Ptr pa(new A())
Shared_Ptr pb(new B())
pa->spa = pb;pb->spb = pa;
当退出后:
现在变成了:
也就是说spa、spb没有释放,所以此时运行后会有A() B(),但是没有~A() ~B()
五、weak_ptr
弱智能指针的出现就是为了解决强智能指针相互引用的问题的。
- 不添加引用计数,单纯管理堆内存
- 结合强智能指针一起使用,不能单独使用(析构函数不释放内存)
如果是类相互引用,那么应该是下面这样写,并且搭配上强智能指针:
template<typename T>
class Weak_ptr
{
public:
Weak_ptr(T* ptr = NULL)
:mptr(ptr)
{}
Weak_ptr(Weak_ptr<T>& rhs)
:mptr(rhs.mptr)
{}
Weak_ptr<T>& operator=(Weak_ptr<T>& rhs)
{
if(this != &rhs)
{
mptr = rhs.mptr;
}
return *this;
}
Weak_ptr<T>& operator=(Shared_Ptr<T>& rhs)
{
mptr = rhs.getPtr();
return *this;
}
~Weak_ptr(){}
T& operator*()
{
return *mptr;
}
T& operator->()
{
return mptr;
}
T* getPtr()
{
return mptr;
}private:
T* mptr;
};
class B;
class A
{
public:
A()
{
std::cout << "A::A()" << std::endl;
}
~A()
{
std::cout << "A::~A()" << std::endl;
}
public:
Weak_ptr<B> spa;
};
class B
{
public:
B()
{
std::cout << "B::B()" << std::endl;
}
~B()
{
std::cout << "B::~B()" << std::endl;
}
public:
Weak_ptr<A> spb;
};
输出:
A::A()
B::B()
B::~B()
A::~A()
请按任意键继续. . .
可见解决了内存泄漏的问题。