在使用C++的过程中我们对内存的管理是一件让人很头疼的事情。在使用new和malloc的过程中,我们往往会忘记调用delete或free去释放已经不再所使用的堆内存,导致了内存泄露的情况。内存泄露会造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
为了能保证手动申请的内存都能够被释放,C++中就引入了智能指针这个概念。我们知道在C++中对象的生成会调用构造函数,当一个对象的生存周期结束时,系统会调用析构函数去释放它所占有的资源。智能指针就是利用了这个特性,用对象来代替指针。在对象的生存周期结束时就可以让系统去释放申请的堆内存。
在C++11版本之前C98版本只有一个智能指针auto_ptr。到了C++11版本之后,又新引进了三种智能指针unique_ptr、shared_ptr、weak_ptr,这四种智能指针各不相同,各有特点,现在auto_ptr已经基本被摒弃了,不在使用。
一、auto_ptr
在使用智能指针的过程中,如果多个智能指针指向同一块堆内存,如果这种情况不加以处理,就会造成多次释放同一块堆内存,auto_ptr采用了所有权唯一的方法,它指定新的智能指针取代旧智能指针的所有权。即赋值或者拷贝会导致旧的智能指针失效。
#include<iostream>
template<typename T>
class Auto_ptr
{
public:
Auto_ptr(T* ptr):mptr(ptr){
std::cout<<"Auto_ptr(T* ptr)"<<std::endl;
}
~Auto_ptr()
{
delete mptr;
mptr = NULL;
std::cout<<"~Auto_ptr(T* ptr)"<<std::endl;
}
Auto_ptr(Auto_ptr<T>& rhs)
{
mptr = rhs.Release();
std::cout<<"Auto_ptr(Auto_ptr<T>& rhs)"<<std::endl;
}
Auto_ptr<T>& operator=(Auto_ptr<T>& rhs)
{
if(this!= &rhs)
{
delete mptr;
mptr = rhs.Release();
}
return *this;
}
T& operator*()
{
return *mptr;
}
T* operator->()
{
return mptr;
}
private:
T* Release()
{
T* tmp = mptr;
mptr = NULL;
return tmp;
}
T* mptr;
};
int main()
{
Auto_ptr<int> p1(new int);
Auto_ptr<int> p2(p1);
int *p = new int;
Auto_ptr<int> p3(p);
return 0;
}
二、unique_ptr
相同的问题,unique_ptr为了使所有权唯一,它禁止智能指针使用拷贝构造和赋值(赋值运算符的重载)。
template<typename T>
class Unique_ptr
{
public:
Unique_ptr(T* ptr):mptr(ptr){
std::cout<<"Unique_ptr(T*)"<<std::endl;
}
~Unique_ptr()
{
delete mptr;
mptr = NULL;
std::cout<<"~Unique_ptr()"<<std::endl;
}
T& operator*()
{
return *mptr;
}
T* operator->()
{
return mptr;
}
private:
/*
将拷贝构造函数和赋值运算符的重载函数的声明写成私有,可以防止调用
*/
Unique_ptr(Unique_ptr<T>&);
Unique_ptr<T> operator=(Unique_ptr<T>&);
T* mptr;
};
int main()
{
Unique_ptr<int> p1(new int);
return 0;
}
三、shared_ptr
shared_ptr,强智能指针,使用时需要包含头文件<memory>,它采用了引用计数的办法共享了空间的所有权,每多一个指针指向这一块内存,就将这一块内存的引用计数加1,当指针的生存周期结束时,先将这块内存的引用计数减1,再查看这个指针指向空间的引用计数是否为0,若为0,则释放这块空间,反之,不用释放。
#include<iostream>
//引用计数器类
class Ref_manage
{
public:
static Ref_manage* GetInstance()
{
if(prm == NULL)
prm =new Ref_manage();
return prm;
}
void addref(void* ptr)
{
if(ptr == NULL)
return;
int index = findref(ptr);
if(index != -1)
{
ref_count[index].count++;
}
else
{
ref_count[cursize].addr = ptr;
ref_count[cursize++].count = 1;
}
}
void delref(void* ptr)
{
if(ptr == NULL)
return;
int index = findref(ptr);
if(index != -1)
{
if(getref(ptr) != 0)
{
ref_count[index].count--;
}
}
}
int getref(void* ptr)
{
if(ptr == NULL)
return -1;
int index = findref(ptr);
if(index == -1)
{
throw std::exception("ptr is error");
}
return ref_count[index].count;
}
private:
Ref_manage()
{
cursize = 0;
}
Ref_manage(const Ref_manage& );
int findref(void* ptr)
{
int index = -1;
for(int i = 0;i<cursize;i++)
{
if(ref_count[i].addr == ptr)
{
index = i;
break;
}
}
return index;
}
typedef struct
{
void* addr;
int count;
}Ref;
Ref ref_count[10];
int cursize;
static Ref_manage* prm;
};
Ref_manage* Ref_manage::prm = NULL;
//Shared_ptr
template <typename T>
class Shared_ptr
{
public:
Shared_ptr(T* ptr)
{
mptr = ptr;
rm->addref(mptr);
std::cout<<"Shared_ptr(T*)"<<std::endl;
}
Shared_ptr(Shared_ptr<T>& rhs)
{
mptr = rhs.mptr;
rm->addref(mptr);
std::cout<<"Shared_ptr(Shared_ptr<T>& rhs)"<<std::endl;
}
Shared_ptr<T>& operator=(Shared_ptr<T>& rhs)
{
std:;cout<<"Shared_ptr<T>& operator=(Shared_ptr<T>&)"<<std::endl;
if(this != &rhs)
{
rm->delref(mptr);
if(rm->getref(mptr) == 0)
{
delete mptr;
}
mptr = rhs.mptr;
rm->addref(mptr);
}
return *this;
}
~Shared_ptr()
{
std::cout<<"~Shared_ptr()"<<std::endl;
rm->delref(mptr);
if(rm->getref(mptr) == 0)
{
delete mptr;
}
mptr = NULL;
}
T& operator*()
{
return *mptr;
}
T* operator->()
{
return mptr;
}
private:
T* mptr;
static Ref_manage* rm;
template< typename E>
friend class Weak_ptr;
};
template<typename T>
Ref_manage* Shared_ptr<T>::rm = Ref_manage::GetInstance();
int main()
{
Shared_ptr<int> p1(new int);
Shared_ptr<int> p2(p1);
Shared_ptr<int> p3 = p2;
return 0;
}
shared_ptr智能指针不仅可以解决内存泄露的问题,巧妙的处理了当多个地方需要共享一块内存时这块内存的释放问题。因为它有引用计数的机制,这就能使他在多个地方同时使用这块内存时,还能知道此时有多少人还在使用着这块内存,当没人使用的时候它才会释放这块内存。
虽然shared_ptr智能指针已经看似完美的解决了大部分问题。但还有一个问题是shared_ptr指针不能解决的,那就是当多个shared_ptr指针相互交叉引用的问题。
#include<iostream>
#include<memory>
class B;
class A
{
public:
A()
{
std::cout<<"A::A()"<<std::endl;
}
~A()
{
std::cout<<"A::~A()"<<std::endl;
}
Shared_ptr<B> spa;
};
class B
{
public:
B()
{
std::cout<<"B::B()"<<std::endl;
}
~B()
{
std::cout<<"B::~B()"<<std::endl;
}
Shared_ptr<A> spb;
};
int main()
{
Shared_ptr<A> p1(new A);
Shared_ptr<B> p2(new B);
p1->spa = p2;
p2->spb = p1;
return 0;
}
我们可以看到直到程序运行结束,也没有释放掉A和B的空间,造成了内存泄露。原因如下:


如图1所示,创建了两个shared_ptr指针p1和p2 ,分别指向个内存块A和B,A对象中有一个B对象的shared_ptr指针spa,B对象中有一个A对象的shared_ptr指针spb。此时A内存和B内存的引用计数都为1.
如图2所示,将A中的spa指向对象B,将B中的spb指向对象A,此时A和B内存的引用计数都为2.

如图3所示,p1和p2的生存周期结束,调用析构函数,将A和B的引用计数分别减1,此时内存A和内存B的引用计数都为1,shared_ptr不会去释放内存,程序结束退出,造成了内存泄露。
针对于这种问题,于是就引入了weak_ptr。
四、weak_ptr
弱智能指针weak_ptr一般结合强智能指针来使用,它更像是强智能指针shared_ptr的一个助手。它并不具备普通指针的行为,并没有重载operator* 和operator ->,它最大的作用就是协助shared_ptr工作,像旁观者一样观察资源的使用情况。
weak_ptr与shared_ptr指向同一块空间时,weak_ptr的指向并不会增加引用计数器。并且weak_ptr也不能单独使用(它并为重载*运算符和->运算符)。
//weak_ptr
template <typename T>
class Weak_ptr
{
public:
Weak_ptr(T* ptr = NULL):mptr(ptr){}
Weak_ptr& operator=(Shared_ptr<T>& rhs)
{
mptr = rhs.mptr;
return *this;
}
private:
T* mptr;
};
//交叉引用问题
class B;
class A
{
public:
A()
{
std::cout<<"A::A()"<<std::endl;
}
~A()
{
std::cout<<"A::~A()"<<std::endl;
}
Weak_ptr<B> spa;
};
class B
{
public:
B()
{
std::cout<<"B::B()"<<std::endl;
}
~B()
{
std::cout<<"B::~B()"<<std::endl;
}
Weak_ptr<A> spb;
};
int main()
{
Shared_ptr<A> p1(new A);
Shared_ptr<B> p2(new B);
p1->spa = p2;
p2->spb = p1;
return 0;
}
我们可以看到weak_ptr和shared_ptr相互结合使用,就可以避免这种内存泄露问题。这是因为在将shared_ptr的指向赋值给weak_ptr时并不增加引用计数的值。