为什么会有智能指针???
C++程序员在编写代码的过程往往都会涉及到堆内存的开辟和释放,使用new和delete关键字。特别是内存的释放是通过程序员手动完成的,而不像栈内存只要生存周期结束即可由系统自动回收。所在在实际的编码中,如果忘记手动释放内存或因其他一些细节原因而未进行堆内存的释放,最终导致产生大量的内存释放,造成资源浪费。
一些特殊的情况导致堆内存没有机会得到释放
int main(){
int* p = new int;
if(条件表达式){
return 0;
}
delete p;
}
一般将堆内存的释放安排到程序的末尾,但是像上述例子,如果程序当中一些逻辑语句满足是就退出程序,最终导致堆内存资源未得到释放。
void fun(int* p){
delete p;
p = NULL;
}
int main(){
int* p = new int;
fun(p);
delete p;
}
在上述代码中,在fun函数中已经将指针p所指向的堆内存进行释放,当fun函数调用完毕后,再次执行delete p,此时程序会崩溃,因为p已经为NULL,指向虚拟地址空间的(void*)0是不允许被访问的内存区域,而现在要对他进行释放,显然是错误的。
int Div(int a,int b){
if(b==0){
throw "div by zero condition!";
}
return a/b;
}
int main(){
int *p = new int;
try{
Div(10,0);
}
catch(const char* meg){
cerr<<msg<<endl;
}
delete p;
}
在上述的代码中,由于在try块中捕获到了异常,直接执行catch中的语句,而未进行堆内存的释放,最终也导致了内存泄漏。
综上的三个例子,对于C++使用堆内存资源并造成内存泄漏的几率还是非常大的,即使你非常小心仔细的处理每一个可能会被泄漏的堆内存,也难免会出现一些问题。这无疑要求C++程序员对于对内存的使用要求之高,而智能指针的诞生解放了C++程序员对于堆内存的管理。
智能指针
智能指针是一个类,它将裸指针(带*的指针)进行了封装,实现的指针的自动释放,它的高明之处就在于程序员只需要一次性的设计出一个具有良好功能的智能指针类,用它实例化出来的对象会自动对对象内存的堆资源进行管理,而不需要程序员去干涉,它自己就可以很好的完成对堆内存的管理。
使用智能指针的前提是利用了当栈对象的生存周期结束时,会自动调用析构函数,来进行对对象的销毁。RAII技术。智能指针不能再堆上创建。
设计智能指针的类模板
需要解决的问题:
(1)指针可以做的事情,智能指针也必须可以做。需要对*
,->
运算符进行重载。
(2)*
运算符需返回引用,因为*
可以连续使用。
(3)*
和->
的重载函数是没有形式参数的。
template <typename T>
class SmartPtr{
public:
SmartPtr(T* ptr=NULL):_ptr(ptr){}
~SmartPtr(){delete _ptr;_ptr=NULL;}
T& operator*(){
return *_ptr;
}
const T& operator*() const{
return *_ptr;
}
T* operator->(){
return _ptr;
}
const T* operator->()const{
return _ptr;
}
private:
T* _ptr;
};
int main(){
SmartPtr<int> p(new int);
*p = 20;
cout<<"*p="<<*p<<endl;
}
智能指针的浅拷贝问题
int main(){
SmartPtr<int> p(new int);
*p = 20;
SmartPtr<int> p1(p);
//程序意图使得p和p1指向同一块堆内存 new int
cout<<"*p="<<*p<<endl;
}
然而执行结果出错,在linux直接检测出来了内存泄漏
错误的原因
两个智能指针对象中的_ptr指向了堆内存的同一块内存区域,但是p1对象析构时释放了该堆内存,而对象p析构时又对堆内存进行进行了释放。然而此时堆内存已经由上一个对象p1进行了释放,所以导致出错。
解决智能指针浅拷贝的问题
引用计数:引用计数实际上就是为了解决这种浅拷贝问题诞生,每当对资源(堆内存)引用一次就对计数器+1,每当删除一次,就对计数器-1,直到当资源的引用计数为0时,就证明没有对象对它进行引用了,此时调用析构函数对资源进行释放。
管理资源的引用计数
//资源的引用计数表
calss resCountMap{
public:
//增加资源的引用计数
void addRef(void* ptr){
_resCntMap[ptr] += 1;//要考虑线程安全 加锁
}
//减少资源的引用计数
void delRef(void* ptr){
//要考虑线程安全 加锁
if(_resCntMap[ptr]==0)
_resCntMap.erase(ptr);
else
_resCntMap[ptr] -= 1;
}
//获取资源的引用计数
int getRef(void* ptr){
return _resCntMap[ptr];
}
private:
map<void*,int> _resCntMap;
};
具有引用计数的智能指针
template <typename T>
class SmatPtr{
public:
SmatPtr(T* ptr=NULL):_ptr(ptr){
if(_ptr!=NULL){
addRef();
}
}
~SmartPtr(){
delRef();
if(getRef()==0){
delete _ptr;
}
}
SmartPtr(const SmartPtr<T>& src):_ptr(src._ptr){
if(_ptr!=NULL){
addRef();
}
}
SmartPtr<T>& operator=(const SmartPtr<T>& src){
if(&src==this)
return *this;
delRef();//将引用计数-1,因为当前智能指针要去引用其他资源
if(getRef()==0){
delete this->_ptr;
}
_ptr = src._ptr;
if(_ptr!=NULL)
addRef();
return *this;
}
T& operator*(){
return *_ptr;
}
const T& operator*() const{
return *_ptr;
}
T* operator->(){
return _ptr;
}
const T* operator->()const{
return _ptr;
}
void addRef(){_countMap.add(_ptr);}
void delRef(){_countMap.delRef(_ptr);}
int getRef(){return _countMap.getRef(_ptr);}
private:
T* _ptr;
static resCountMap _countMap;//是所有智能指针对象公有的,设置为static
};
//在类外进行静态成员函数的初始化
template <typename T>
resCountMap SmatPtr<T>::_countMap;