我们都知道在堆上开辟内存需要手动释放,这就很容易因为忘记释放或者程序在还没有执行到释放代码时已经返回而导致内存泄露。智能指针就可以很好的解决这个问题。智能指针实际上是一个类类型,在使用智能指针的时候会实例化出一个对象,该对象在构造函数中开辟空间,在析构函数中释放空间,它利用了栈上对象在出栈时会自动析构的特点使得我们无需手动管理堆上的资源。所以不能把智能指针本身定义在堆上,否则其存在就没有意义了。
智能指针分为有引用计数的和没有引用计数的:
有引用计数:shared_ptr、boost::weak_ptr
无引用计数:std::auto_ptr、std::unique_ptr、boost::scoped_ptr
所有智能指针一般都会重载->和*操作符,从而可以采用标准指针语法来使用它们,为了解决浅拷贝,智能指针还必须提供拷贝构造函数以及赋值重载函数,但是在拷贝构造时并不会开辟新的内存,因为这与我们使用指针拷贝构造或赋值的初衷不符,为了实现多个指针可以指向同一块资源但该资源却只会析构一次(delete一次),不同的智能指针有不同的解决方法:
auto_ptr
采用所有权的方式,在给其他auto_ptr赋值的时候,会转移这种拥有关系,可能会导致指针悬空,运行崩溃,所以不能让两个auto_ptr指向同一个对象,即不要使用“operator=”操作符。因而auto_ptr也不能作为容器对象,因为STL容器中的元素经常要支持拷贝,赋值等操作,在这过程中auto_ptr会传递所有权,这很明显违背了我们的使用初衷。
unique_ptr
同样采用所有权模式来解决这种问题,但在使用unique_ptr时,程序不会等到运行阶段崩溃,而在编译时出现错误,可以避免潜在的内存崩溃问题。因为当指针所指对象被指定到另一个指针时它指向的对象会被摧毁。所以可以说它持有的是对对象的独有权,两个unique_ptr不能指向一个对象。
scoped_ptr
则独享所有权,明确拒绝用户写“my_memory2 = my_memory”之类的语句。
由此可见,没有引用计数的智能指针在使用上都有很大的缺陷,不但无法很好的解决不同指针指向同一块资源的问题,也无法实现一个智能指针可以管理不同资源的问题,所以我们经常会使用一种带有引用计数的智能指针,以此来方便的管理资源的开辟和释放。需要注意的是,带引用的智能指针是线程安全的,虽然其内部的引用计算本身不是线程安全的,但是智能指针内部用atomic对其进行了多线程中的互斥操作。
智能指针的交叉引用
智能指针的出现解决了裸指针易出现资源泄露的问题,但是智能指针的交叉使用同样会导致资源泄露。
所谓智能指针的交叉使用,如下代码所示:
class A
{
public:
shared_ptr<B> _pb;
};
class B
{
public:
shared_ptr<A> _pa;
};
int main()
{
shared_ptr<A> pa(new A);
shared_ptr<B> pb(new B);
pa->_pb = pb;
pb->_pa = pa;
}
图示如下:
由于出栈时先析构pb,再析构pa,显而易见,由于A、B的引用计数只能减为1,其资源无法释放。
要解决这个问题,会引入一个新的概念:强弱智能指针。shared_ptr是强智能指针,持有对资源的引用计数,而weak_ptr是弱智能指针,它也有引用计数,但是这个引用计数不是针对资源的,weak_ptr可以看做一个针对shared_ptr的观察者,其引用计数记录了观察该强智能指针的weak_ptr个数。当被观察的shared_ptr 失效后,相应的weak_ptr 也相应失效。引入弱智能指针就可以解决强智能指针交叉引用导致的资源泄露问题。
只需要引入一个weak_ptr,问题即可解决:
class A
{
public:
weak_ptr<B> _pb;
};
class B
{
public:
shared_ptr<A> _pa;//同样改为weak_ptr<A> _pa;更加合适
};
此时分析上述代码:在析构的时候先析构pb,由于B的引用计数减一后为0,所以会调用B的析构函数,此时会析构类B的成员_pa,使得A资源的引用计数减一,接着析构pa,这个时候A资源的引用计数减一后为0,所以A资源被析构,资源泄露得以解决。
创建对象的时候使用强智能指针,其他地方一律持有弱智能指针,这样可以有效避免资源泄露问题。这是使用带引用计数只能指针时需要注意的地方。
弱智能指针无法直接访问资源,因为它持有的并不是资源本身,而是指向资源的强智能指针。因此在weak_ptr中有一个很重要的方法:lock(),即弱智能指针的提升操作,可将其提升为强智能指针,如果此时强智能指针所指资源已经析构,则提升失败,否则返回提升成功的强智能指针,相应资源的引用计数也会加一。
多线程中共享C++对象的线程安全
智能指针在使用时主要由两方面需要注意,一即智能指针交叉引用的问题,其实是指强智能指针的交叉引用,上面已经提到,二就是多线程中共享C++对象的线程安全问题:
在多线程中,由于线程运行先后顺序无法确定,在使用共享的对象时存在竞态条件,即共享对象的存活问题,这导致我们的代码是非线程安全的。示例如下:
class A
{
public:
void fun(){cout<<"call fun()"<<endl;}
};
void * threadProc(void *lparg)
{
A *p = (A*)lparg;//即主线程中的A a
/*在使用a对象的时候需要确定其是否存活,因为两个线程的执行顺序是不确定的,如果主线程已经结束,那么a对象已经析构,此时无法在此线程中再使用它*/
p->fun();
}
int main()
{
A a;
pthread_t tid;
pthread_create(&tid,NULL,threadProc,&a);
return 0;
}
而强弱智能的配合使用可以解决多线程访问共享c++对象所产生的线程安全问题,此时需要用到上述所强调的地方(创建对象的时候使用强智能指针,其他地方一律持有弱智能指针)以及lock函数:
void * threadProc(void *lparg)
{
weak_ptr<A> p = *(weak_ptr<A>*)lparg;
shared_ptr<A> pa = p.lock();//弱智能指针的提升操作,将其提升为强智能指针
if(pa != NULL)
{
//资源引用计数加一
pa->fun();
}
else
{
//提升失败,说明对象已经析构
}
return NULL;
}
int main()
{
shared_ptr<A> pa(new A);
pthread_t tid;
weak_ptr<A> pw(pa);
pthread_create(&tid,NULL,threadProc,&pa);
return 0;
}
此时,无论哪个线程先运行,都绝不会出现使用已析构对象的情况。
注:智能指针自实现版
class Test
{
public:
Test(){cout<<"Test "<<endl;}
~Test(){cout<<"~Test "<<endl;}
void fun(){cout<<"fun"<<endl;}
};
class CHeapManger
{
public:
static CHeapManger& getIntance()
{
return heapmanager;
}
private:
CHeapManger(){}
public:
void addRef(void *ptr)
{
vector<ResItem>::iterator it = find(_vec.begin(),_vec.end(),ptr);
if(it == _vec.end())
{
ResItem item(ptr);
_vec.push_back(item);
}
else
{
it->_refcount++;
}
}
void delRef(void *ptr)
{
vector<ResItem>::iterator it = find(_vec.begin(),_vec.end(),ptr);
if(it != _vec.end())
{
it->_refcount--;
}
}
int getRef(void *ptr)
{
vector<ResItem>::iterator it = find(_vec.begin(),_vec.end(),ptr);
if(it != _vec.end())
{
return it->_refcount;
}
throw "no this source";
}
private:
struct ResItem
{
ResItem(void *ptr = NULL):_paddr(ptr),_refcount(0)
{
if(_paddr != NULL)
_refcount = 1;
}
bool operator==(void *ptr)
{
return _paddr == ptr;
}
void *_paddr;
int _refcount;
};
vector<ResItem> _vec;
static CHeapManger heapmanager;
};
CHeapManger CHeapManger::heapmanager;
template <class T>
class CSmartPtr
{
public:
CSmartPtr(T* ptr = NULL):_ptr(ptr)
{
if(_ptr != NULL)
addRef();
}
~CSmartPtr()
{
delRef();
if(0 == getRef())
delete _ptr;
}
CSmartPtr<T>& operator=(const CSmartPtr &src)
{
if(this == &src)
return *this;
delRef();
if(0 == getRef())
delete _ptr;
_ptr = src._ptr;
if(_ptr != NULL)
addRef();
}
CSmartPtr(const CSmartPtr &src):_ptr(src._ptr)
{
if(_ptr != NULL)
addRef();
}
public:
T& operator*(){return *_ptr;}
const T& operator*()const{return *_ptr;}
T* operator->(){return _ptr;}
const T* operator->()const{return _ptr;}
void addRef(){_heapManger.addRef(_ptr);}
void delRef(){_heapManger.delRef(_ptr);}
int getRef(){return _heapManger.getRef(_ptr);}
private:
T* _ptr;
static CHeapManger &_heapManger;
};
template<class T>
CHeapManger& CSmartPtr<T>::_heapManger = CHeapManger::getIntance();
int main()
{
int *p = new int;
CSmartPtr<int> p1(p);
CSmartPtr<char> p2((char*)p);
//p1和p2是两个类型,各自有自己的heapManger
//所以此时程序会崩溃
//在这里需要使用单例模式,无论用类CHeapManger定义此对象都只能实例化出一个对象
return 0;
};