目录
一、智能指针的引入
我们都知道,栈上的内存是程序自己开辟自己释放,而堆上的内存是由程序员手动开辟,手动释放。由于C++语言不像java语言自带垃圾回收机制,在利用C++语言编程过程中,我们一般用new进行动态申请内存,使用完后,必须记得最终要用delete释放堆内存,但程序员也是人,可能会在不经意间忘记delete已经开辟过的内存,这就会导致内存泄漏。例如下面的例子:
#include <iostream>
using namespace std;
void func(int *ptr)
{
int *p=new int;
if(ptr==NULL)
{
throw exception("ptr is null");
}
*p=*ptr;
delete p;
}
int main()
{
func(NULL);
return 0;
}
当程序抛出异常后,有专门的异常处理机制去处理,那么久无法释放堆内存,从而导致内存泄漏的问题。
那么这时候,我们就可以用智能指针来解决:
void func(int *ptr)
{
auto_ptr<int> p(new int);
if(ptr==NULL)
{
throw exception("ptr is null");
}
*p=*ptr;
//delete p;
}
上面若加上对异常进行处理的代码,就不会出现崩溃。所以在这样的一个背景下,C++就引入了智能指针的思想。
注意:在编程中若要用到标准库里的智能指针需引入头文件#include<memory>
二、什么是智能指针?
智能指针:面向对象的指针,以对象的方式存在,具有指针的功能
栈上的内存:系统开辟 系统清理
堆上的内存:手动开辟 手动释放
智能指针是为了实现系统清理我们手动开辟的堆内存而设计的一种思想。
我们知道,对象销毁时,做了两步:
(1)调用析构函数
(2)释放空间
即就是栈上的一个对象的一个成员变量指向了一个堆内存
当栈上的对象生存周期到了之后,系统对其进行销毁,销毁时把堆内存的释放放到对象的的析构函数里:
{
delete mptr;
}
这样,系统自动释放栈上对象的空间时, 调用对象的析构函数会自动释放对象中mptr指针指向的堆内存,就达到了堆内存手动开辟,系统释放的目的。这就是智能指针设计的思想。
三、智能指针的分类
智能指针主要分为boost库里的智能指针和C++标准库里的
在C++11标准以前,标准库里只有auto_ptr一个智能指针,因为auto_ptr有很大的缺陷,所以C+11以后把它弃用了,引入了3个智能指针,分别是unique_ptr,shared_ptr,weak_ptr。所以c+11以后,标准库里有4个智能指针
而boost库里的智能指针有:scope_ptr,scope_array,shared_array等。
其中C++标准库里的unique_ptr类似于boost库里的scope_ptr
3.1 auto_ptr
auto_ptr:管理权唯一,即sp1指向一个堆内存,当有新的智能指针对象sp2指向这块堆内存时,sp1就会指向空
auto_ptr的简单实现:
/*
代码一:简单的auto_ptr
*/
#include <iostream>
using namespace std;
template<typename T>
class Auto_Ptr
{
public:
Auto_Ptr(T* ptr = NULL) :mptr(ptr){}//构造
Auto_Ptr(const Auto_Ptr<T>& rhs) :mptr(rhs.mptr)//拷贝构造
{
rhs.Release();
}
Auto_Ptr<T>& operator=(const Auto_Ptr<T>& rhs)//赋值运算符的重载函数
{
if (this != &rhs)
{
this->~Auto_Ptr();
mptr = rhs.mptr;// NULL | address
rhs.Release();
}
return *this;
}
~Auto_Ptr()
{
delete mptr;
mptr = NULL;
}
T& operator*()
{
return *mptr;
}
T* operator->()
{
return mptr;
}
private:
void Release()const //const Auto_Ptr<T>* const
{
(T*)mptr = NULL;//改变旧智能指针的指向,让其指向空
}
T* mptr;
};
int main()
{
Auto_Ptr<int> sp1(new int);
Auto_Ptr<int> sp2 = sp1;
sp2 = sp1;
*sp1 = 10;
return 0;
}
如果多个智能指针指向一个堆内存时,只有一个智能指针具有管理权(其他智能指针被置空),导致其他智能指针失效,当用没有管理权的智能指针进行操作时,会导致程序崩溃。
原因:管理权唯一(管理权包括对它的访问(修改)权和释放权)
解决方法:若管理权不唯一,仅释放权唯一,就不会导致其他智能指针失效。
这时就可以加一个标志flag,在智能指针的析构函数中,如果flag为true说明对象可以释放,若为false说明不能释放
释放权转移的问题,多个智能指针指向同一个对内存时,释放权是如何转移的?
释放权转移方法:
当旧的智能指针有释放权,新的就有,将旧的释放权取消
当旧的智能指针没有释放权,新的也没有
即:
new.flag=old.flag;
old.flag=false;
带标志位flag的auto_ptr智能指针的实现:
/*
代码二:带标记位的auto_ptr
*/
#include <iostream>
using namespace std;
template<typename T>
class SmartPtr
{
public:
SmartPtr(T* ptr) :mptr(ptr), flag(true){}//第一个智能指针赋予释放权
SmartPtr(const SmartPtr<T>& rhs) :mptr(rhs.mptr)//拷贝构造函数
{
flag = rhs.flag;
rhs.flag = false;//常对象里的成员变量不可修改,所以下面私有成员变量将flag加mutable去除常性就可
}
SmartPtr<T>& operator=(const SmartPtr<T>& rhs)//赋值运算符的重载函数
{
if (this != &rhs)
{
this->~SmartPtr();//看旧智能指针是否有其他堆内存的释放权
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:
T* mptr;
mutable bool flag;//mutable去除常性,flag标记释放权
};
int main()
{
SmartPtr<int> sp1(new int);
SmartPtr<int> sp2(sp1);
SmartPtr<int> sp3(sp1);
*sp1 = 20;
return 0;
}
带标志位的auto_ptr也有其缺点:
void func(SmartPtr<int> sp)
{}
int main()
{
SmartPtr<int> sp1(new int);
SmartPtr<int> sp2(sp1);
SmartPtr<int> sp3(sp1);
func(sp2);
//调用拷贝构造函数构造了形参对象sp,释放权给了形参对象,而形参对象是在函数运行结束后就销毁了
*sp1 = 10;//在进行解引用时,运行没问题,但这样操作相当于操作了已经归还给系统的内存块了
//(野指针:指向的地址可以操作,但该堆内存可能会被分配出去,而导致数据被修改而出错)
return 0;
}
由上述代码可知,带标志位的auto_ptr的缺点就是,堆内存的提前释放,可能会导致其他智能指针的使用出错。
auto_ptr的这些缺点,最终原因就是它允许多个智能指针指向同一堆内存。那我们下面要说的scope_ptr就不一样了,继续向下看吧。
3.2 scope_ptr
scope_ptr是boost库里最简单的智能指针,它的设计思想是:一块堆内存只能被一个智能指针所引用,即不允许权限转移(哈哈,非常的暴力是不是)
怎么实现呢?
将拷贝构造函数和赋值运算符的重载函数屏蔽起来,即不存在多个智能指针指向同一个堆内存的问题。
简单的scope_ptr的实现代码:
/*
代码三:scope_ptr
*/
#include <iostream>
using namespace std;
template<typename T>
class Scope_Ptr
{
public:
Scope_Ptr(T* ptr) :mptr(ptr){}
~Scope_Ptr()
{
delete mptr;
}
T& operator*()
{
return *mptr;
}
T* operator->()
{
return mptr;
}
private:
Scope_Ptr(const Scope_Ptr<T>&);//拷贝构造
Scope_Ptr<T>& operator=(const Scope_Ptr<T>&);//赋值运算符的重载
T* mptr;
};
int main()
{
int* p = new int;
Scope_Ptr<int> sp1(p);
Scope_Ptr<int> sp2(p);
Scope_Ptr<int> sp3(p);
//在构造时,人为的让多个智能指针指向同一块堆内存,析构时重复释放堆内存,导致程序崩溃
return 0;
}
scope_ptr存在的问题:当在外部人为地控制多个智能指针指向同一个内存块时,无法进行正确操作(即当一个智能指针进行释放后,会销毁堆内存使得其余的智能指针称为野指针);并且,一个智能指针指向一个内存块,可能导致内存浪费严重等问题;
3.3 shared_ptr强智能指针
shared_ptr带有引用计数的智能指针。引用计数是指有多少个智能指针对象管理这个堆内存
设计思想:使用一个引用计数管理器类,和一个智能指针类
引用计数管理器类中只会做增加或减少引用计数的操作,而堆内存的释放是在智能指针类中做的。
增加智能指针时:获取到智能指针所指向的内存地址后,先在引用计数管理器中匹配有无该地址,有,则给该地址对应的引用计数加1;无,增加地址域并将对应的引用计数置1;
释放智能指针时:先判断引用计数,大于0时,减引用计数;小于0,时,抛出异常,等于0时,释放堆内存;
另外注意:引用计数管理器类应设计为单例模式。
shared_ptr的代码实现如下所示:
/*
代码四:shared_ptr
*/
#include <iostream>
using namespace std;
class Ref_Management//设计为单例模式
{
public:
static Ref_Management* getInstance()//对外提供唯一对象
{
return &rm;
}
private:
Ref_Management() :cursize(0){}
Ref_Management(const Ref_Management& rhs);
public:
void addref(void* ptr)
{
int index = find(ptr);
if (index < 0)
{
//node[cursize].addr = ptr;
//node[cursize++].ref = 1;
Node tmp(ptr, 1);
node[cursize++] = tmp;
}
else
{
node[index].ref++;
}
}
void delref(void* ptr)
{
int index = find(ptr);
if (index < 0)
{
throw std::exception("ptr is not exsit!");
}
else
{
if (getref(ptr) != 0)
{
--node[index].ref;
}
}
}
int getref(void* ptr)
{
int index = find(ptr);
if (index < 0)
{
throw std::exception("ptr is not exsit!");
}
else
{
return node[index].ref;
}
}
private:
int find(void* ptr)
{
int rt = -1;
for (int i = 0; i < 10; i++)
{
if (node[i].addr == ptr)
{
rt = i;
break;
}
}
return rt;
}
class Node
{
public:
Node(void* pa = NULL, int r = 0) :addr(pa), ref(r){}
public:
void* addr;
int ref;
};
Node node[10];
int cursize;//有效 当前可插入的下标位置
static Ref_Management rm;
};
Ref_Management Ref_Management::rm;//.data
template<typename T>
class Shared_Ptr //Shared_Ptr<int>:: rm Shared_Ptr<double>:: rm;
{
public:
Shared_Ptr(T* ptr = NULL) :mptr(ptr)
{
AddRef();
}
Shared_Ptr(const Shared_Ptr<T>& rhs) :mptr(rhs.mptr)
{
AddRef();
}
Shared_Ptr<T>& operator=(const Shared_Ptr<T>& rhs)
{
if (this != &rhs)
{
DelRef();
if (GetRef() == 0)
{
delete mptr;
}
mptr = rhs.mptr;
AddRef();
}
return *this;
}
~Shared_Ptr()
{
DelRef();
if (GetRef() == 0)
{
delete mptr;
}
mptr = NULL;
}
T& operator*()
{
return *mptr;
}
T* operator->()
{
return mptr;
}
T* getPtr()const
{
return mptr;
}
private:
void AddRef()
{
rm->addref(mptr);
}
void DelRef()
{
rm->delref(mptr);
}
int GetRef()
{
return rm->getref(mptr);
}
static Ref_Management* rm;
T* mptr;
};
template<typename T>
Ref_Management* Shared_Ptr<T>::rm = Ref_Management::getInstance();//静态成员变量在类外进行初始化
int main()
{
int* p = new int;
Shared_Ptr<int> sp1(p);
Shared_Ptr<int> sp2(p);
Shared_Ptr<int> sp3(p);
Shared_Ptr<int> sp4(p);
Shared_Ptr<double> sp5(new double);
Shared_Ptr<char> sp6(new char);
return 0;
}
强智能指针存在的问题,先看如下代码:
class B;
class A
{
public:
A()
{
std::cout << "A::A()" << std::endl;
}
~A()
{
std::cout << "A::~A()" << std::endl;
}
public:
Shared_Ptr<B> pa;//成员对象也是一个智能指针
};
class B
{
public:
B()
{
std::cout << "B::B()" << std::endl;
}
~B()
{
std::cout << "B::~B()" << std::endl;
}
public:
Shared_Ptr<A> pb;//成员对象也是一个智能指针
};
int main()
{
Shared_Ptr<A> spa(new A());
Shared_Ptr<B> spb(new B());
spa->pa = spb;//智能指针相互引用
spb->pb = spa;
return 0;
}
强智能指针相互引用的问题导致的内存泄漏问题,如下,没有调用析构函数(堆内存没有释放):
spa->pa = spb;
spb->pb = spa;分析这两句做了什么事情:
这两句用蓝色线条表示,表示智能指针相互引用,导致堆内存的引用计数都+1,而销毁时,只有spa和spb两个智能指针进行销毁,引用计数分别进行一次-1后,引用计数都不为0,所以不会进行内存的释放,导致内存泄露
解决方法:引入弱智能指针weak_ptr
3.4 weak_ptr 弱智能指针
弱智能指针只进行管理,不添加引用计数;
并且弱智能指针是不进行内存销毁的(析构函数什么都不做),所以弱智能指针不可以单独使用必须配合强智能指针使用
模拟实现weak_ptr:
template<typename T>
class Weak_Ptr
{
public:
Weak_Ptr(T* ptr = NULL) :mptr(ptr){}
Weak_Ptr(const Weak_Ptr<T>& rhs) :mptr(rhs.mptr){}
Weak_Ptr<T>& operator=(const Weak_Ptr<T>& rhs)
{
if (this != &rhs)
{
mptr = rhs.mptr;
}
return *this;
}
Weak_Ptr<T>& operator=(const Shared_Ptr<T>& rhs)
{
mptr = rhs.getPtr();
return *this;
}
~Weak_Ptr(){}
T& operator*()
{
return *mptr;
}
T* operator->()
{
return mptr;
}
private:
T* mptr;
};
利用强弱指针结合使用,解决智能指针之间的相互引用问题;当智能指针相互引用时,只进行管理,对引用的内存的引用计数不进行加操作。
所以将class A和class B中的强智能指针换成弱智能指针,如下,就不会导致内存泄漏问题:
class B;
class A
{
public:
A()
{
std::cout << "A::A()" << std::endl;
}
~A()
{
std::cout << "A::~A()" << std::endl;
}
public:
Weak_Ptr<B> pa;
};
class B
{
public:
B()
{
std::cout << "B::B()" << std::endl;
}
~B()
{
std::cout << "B::~B()" << std::endl;
}
public:
Weak_Ptr<A> pb;
};
总结:
(1)auto_ptr:管理权唯一,释放权唯一
存在的问题:当多个智能指针指向同一堆内存时,新智能指针具有管理权,原智能指针指向空,导致原智能指针失效
(2)带标志位的auto_ptr:管理权不唯一,释放权唯一
存在的问题:释放权的转移可能导致堆内存的提前释放,而使其他智能指针的使用出错
(3)scope_ptr:一个智能指针只能指向一个堆内存,不允许权限转移
存在的问题:内存浪费严重(对于每一块堆内存,都在栈上生成一个智能指针对象,由该对象指向它)
与boost库里的unique_ptr类似:
unique_ptr:禁止拷贝和移动来保证所有权唯一
unique_ptr存在的问题:(1)不能数据共享;(2)在设计层面保证所有权唯一,但使用时并不能保证
(4)shared_ptr:带有引用计数
存在的问题:智能指针之间的相互引用问题会导致内存泄漏
(5)weak_ptr:不可单独使用,常与强智能指针结合使用,以解决强智能指针相互引用的问题