什么是智能指针?
智能指针是一个类,这个类的构造函数中传入一个指针,析构函数中释放传入的指针。智能指针都是栈上那个的对象,所以当函数(或程序)结束时都会被自动释放。
为什么要使用智能指针?
在堆区申请空间往往会遇到以下问题:
1、内存被不正确的释放,例如下代码:
void fun(int *p)
{
*p = 5;
cout << *p << endl;
delete p;
}
int main()
{
int *p = new int;
fun(p);
cout << *p << endl;
return 0;
}
在main函数中申请了一块空间,但是调用fun函数,fun函数在使用完这块内存后将其释放了,这就导致main再次使用这块空间时出错。在这样的小型代码里可以很直观的就看出问题在哪,但是在大型项目中,可能这两个代码块有不同的人员编写,那么编写main函数的人员就无法得知fun函数中已经将改内存块释放了,在之后的代码中继续使用该内存块就会出错,并且难以找出问题所在。
对于这个问题,其实并不难以解决。按照c++内存使用原则:谁申请,谁释放。按此例来讲main函数申请的内存,智能由main函数释放,就可以解决上述问题。
2、内存何时释放
按照谁申请,谁释放的原则看似万无一失,实则引出了另一个问题,当申请该内存的函数并不知道何时释放该内存时如何处理,这种情况是十分常见的。如下代码:
int* getMemory()
{
return new int;
}
int main()
{
int *p = getMemory();
*p = 5;
cout << *p << endl;
delete p;
return 0;
}
上述代码中getmemory函数功能是申请空间,按照谁申请谁释放的原则,getmemory函数申请的空间就应该由它释放,但是getmemory函数的功能就是申请空间,总不能申请之后又释放。因此只能通过main函数来释放。这样就导致谁申请谁释放的原则看起来完美但在使用的时候并不那么容易。
3、当一个内存块被多个函数使用时,由谁来释放。
上述例子是getmemory函数申请的内存只有main函数在使用,这样在getmemory函数无法释放的时候可以通过main函数将其释放。那么当getmemory申请的空间被更多的函数使用,该由谁来释放呢?
总结:通过对上述问题的分析可以得出,内存块由谁创建不重要,重要的是该由谁去释放。应该是最后一个使用这个内存块的函数来释放它。每个函数在用完之后,都切断这个函数与内存块之间的联系,然后在最后一个函数切断与内存块的联系后,就需要释放这个内存块了。这时候引用智能指针就是通过引用计数,来表明有多少个地方在用这个内存块,当引用计数为零,也就表明没有地方用到这个内存块了,就对这个内存块进行释放。
常见的智能指针
- shared_ptr(强智能指针):基于引用计数的智能指针。可随意赋值,直到内存的引用计数为0的时候这个内存会被释放。
- weak_ptr(弱智能指针):弱引用。引用计数有一个问题交叉引用形成环,这样两个指针指向的内存都无法释放。需要手动打破循环引用或使用weak_ptr。weak_ptr是一个弱引用,只引用不计数。如果一块内存被shared_ptr和weak_ptr同时引用,当所有shared_ptr析构了之后,不管还有没有weak_ptr引用该内存,内存也会被释放。所以weak_ptr不保证它指向的内存一定是有效的,在使用之前需要检查weak_ptr是否为空指针。
shared_ptr智能指针的实现。
template <typename T>
class smartPtr
{
private:
T* _ptr;
static map<T*, int> _num;
public:
smartPtr(T *p)//构造函数
{
_ptr = p;
if (_num.find(p) != _num.end())
{
_num[p]++;
}
else
{
_num.insert(make_pair(p, 1));
}
cout << _ptr << ";" << _num[_ptr] << endl;
}
smartPtr(const smartPtr& src)//拷贝构造
{
_ptr = src._ptr;
_num[src._ptr]++;
}
~smartPtr()//析构函数
{
cout << "~smartPtr()" << endl;
if (--_num[_ptr] == 0)
{
delete _ptr;
}
cout << _ptr << ";" << _num[_ptr] << endl;
}
T& operator*()//重载运算符*
{
return *_ptr;
}
T* operator->()//重载运算符->
{
return _ptr;
}
};
template<typename T>
map<T*, int> smartPtr<T>::_num = map<T*, int>();
注意:交叉引用以及解决办法
首先观察如下代码:
class B;
class A
{
public:
A()
{
cout << "A()" <<endl;
}
~A()
{
cout << "~A()" <<endl;
}
Shared_ptr<B> _ptrb;
};
class B
{
public:
B()
{
cout << "B()" <<endl;
}
~B()
{
cout << "~B()" <<endl;
}
Shared_ptr<A> _ptra;
};
int main()
{
shared_ptr<A> ptra(new A());
shared_ptr<B> ptrb(new B());
ptra->ptrb = ptrb;
ptrb->ptra = ptra;
return 0;
}
显而易见,类A中有一个指向类B的shared_ptr强类型智能指针,类B中有一个指向A的shared_ptr强类型智能指针。此时,有两个强智能指针指向了对象A,对象A的引用计数为2.也有两个强智能指针指向了对象B,对象B的引用计数也为2。当主函数返回后,对象A和B的引用计数都减为1,此时因为引用计数不为0,并没有释放内存,程序结束造成内存泄漏。
解决方法:将类A和类B中的shared_ptr强智能指针都换成weak_ptr弱智能指针。在使用强弱智能指针的时候,有一个规定,就是创建对象的时候,持有它的强智能指针,当其他地方想使用这个对象的时候,应该持有该对象的弱智能指针。
class B;
class A
{
public:
A()
{
cout << "A()" <<endl;
}
~A()
{
cout << "~A()" <<endl;
}
weak_ptr<B> _ptrb;//其他地方持有对象的弱智能指针
};
class B
{
public:
B()
{
cout << "B()" <<endl;
}
~B()
{
cout << "~B()" <<endl;
}
weak_ptr<A> _ptra;//其他地方持有对象的弱智能指针
};
int main()
{
shared_ptr<A> ptra(new A());//创建对象的时候持有强智能指针
shared_ptr<B> ptrb(new B());//创建对象的时候持有强智能指针
ptra->ptrb = ptrb;
ptrb->ptra = ptra;
return 0;
}
weak_ptr弱智能指针,虽然有引用计数,但实际上它并不增加计数,而是只观察对象的引用计数,weak_ptr的引用计数指的是有多少个weak_ptr在观察同一个shared_ptr。而shared_ptr强智能指针的引用计数是对资源的引用计数,所以此时对象A的引用计数只为1,对象B的引用计数也只为1。当主函数return返回后,对象A和B的引用计数减为0,在析构的时候释放内存,不会造成内存泄漏。
强弱智能指针的使用总结:
- 创建对象的时候用shared_ptr强智能指针,别的地方一律持有weak_ptr弱类型指针,否则析构顺序会出现错误。
- 当通过弱智能指针访问对象时,需要先进行lock提升操作,提升成功,证明对象还在,再通过强智能指针访问对象。