为什么会有智能指针?
在C++中,new总是和delete配对使用,而有时候人们总是会把delete忘掉,或者说没有放在合适的位置上,从而导致内存泄漏。智能指针就是用来解决这样的问题的。举例说明。
void func(const std::string &str)
{
std::string *ps = new std::string(str);
...
if(...)
{
return ;
}
...
delete ps;//当 if 成立时函数会直接退出,不会执行 delete
}
有人可能会说这个问题非常的简单,直接在 return 前加上 delete 就行了,但是你能确保每次都能在合适的位置加上它吗?
如果上述 ps 可以像对象一样,在过期后,可以自动释放它所指向的内存,那该有多好。这就是智能指针的设计思想。也就是使用的所谓的RAII思想。
智能指针
智能指针的实现其实就是将基本类型指针封装为类对象指针,并在析构函数里编写delete语句删除指针指向的内存空间。
auto_ptr:STL库提供的智能指针。
1、auto_ptr 不能共享所有权,不能让两个 auto_ptr 指向同一个对象。
std::auto_ptr<int> pi1 = (new int(123));
std::auto_ptr<int> pi2 = pi1;
*pi1 = 100;//error
当给pi2拷贝构造时,pi1已经失去了其对内存的有所有权。使用auto_ptr 时,有且只有一个对象可以持有对象的所有权。
2、auto_ptr 不能指向数组。
std::auto_ptr <int> pi = (new int[256]);//error
一次分配多个对象必须要用delete[]去释放,否则会出错。问题其实就是delete和delete[]的区别问题。delete用于释放单个对象,获取的是对象的地址,并且调用free,对象数组的分配有所不同,除了分配所需大小的堆内存空间外,这段堆内存的开头还分配了4个字节的空间,用于存放分配对象的数量,在释放的时候必须把最开头的四个字节包含进去,delete[]在释放的时候,将得到的地址减4,这样取得了最开始的地址,这样才是正确的释放,而delete的释放并没有减4的操作,因此无法正确的释放。auto_ptr的析构函数中只有delete的释放,所以对象数组必然出错。
3、auto_ptr 不能作为容器对象。
std::vector<std::auto_ptr<int>> vi;
int func()
{
std::auto_ptr<int> ps = vi[0];//vi[0] 会失去对象的所有权,变得没有意义。
}
在容器中对对象的复制,交换、复制、拷贝是非常的比较常见的,此外,某些容器的成员函数可能创建一个或多个元素的拷贝,比如swap()、sort(),因此auto_ptr 不能作为容器对象。
4、不可以通过赋值完成初始化。
int *pi = new int;
std::auto_ptr<int> p1(pi);//ok ①
std::auto_ptr<int> p2 =pi;//error ②
①显式调用auto_ptr的拷贝构造函数,auto_ptr(int*)。
②隐式调用auto_ptr的拷贝构造函数,auto_ptr(int*),但是auto_ptr(int*) ,已经被声明为explicit,无法隐式调用。
将auto_ptr(int*)声明为explicit是为了强调auto_ptr与一般指针的差别,以免使用者隐式调用而不自知。因为一旦你这样调用了 auto_ptr p2 = pi; ,就不应该再调用delete pi了,所以明确知道使用auto_ptr(int*)是有好处的。
unique_ptr:和auto_ptr非常的相似,所以只是简单的阐述一下他们之间的区别。
auto_ptr<int> api1(new int(10));
auto_ptr<int> api2(api1);//ok
auto_ptr<int> api3 = api1;//ok
unique_ptr<int> upi1(new int(10));
unique_ptr<int> upi2 (upi1);//error
unique_ptr<int> upi3 = upi1; //error
unique_ptr不支持拷贝构造和赋值操作,但是却支持移动构造和移动赋值。如下:
unique_ptr<int> Getptr()
{
unique_ptr<int> up(new int(10));
return up;
}
unique_ptr<int> up = Getptr();//ok
unique_ptr<int> up1(new int(10));
unique_ptr<int> up2 = std::move(up1);
//显式的所有权转移,把up1所指的内存转给up2了,up1不再拥有该内存
auto_ptr不可做为容器元素.,但unique_ptr却可以作为容器元素
unique_ptr<int> up(new int(10));
vector<unique_ptr<int>> vec;
vec.push_back(std::move(up));//push_back后up不再拥有内存
auto_ptr支持随意赋值,但赋值完成之后原来的对象便失去了对原有内存的控制权,不重新赋值就不能随意访问 。而unique不支持赋值和拷贝,若必须赋值就必须显式的说明内存转移(std:move),虽然他们都会导致原来的对象失去对原有内存的控制权,但是却让人知道是(std:move)这个操作使原有的unique_ptr失效。
shared_ptr(强智能指针):每一个shared_ptr都有一个关联的计数器,称之为引用计数,无论何时我们拷贝一个shared_ptr,计数器都会增加.(当我们用一个shared_ptr初始化另一个shared_ptr,或将shared_ptr作为参数传递给一个函数,它所关联的计数器都会增加)当我们给shared_ptr一个新的值时或shared_ptr被销毁时,计数器就会减少。
①和auto_ptr相同shared_ptr不能通过赋值完成初始化,必须使用直接初始化形式,显示的调用拷贝构造函数,其原因和auto_ptr相同。
shared_ptr<int> sp1 = new int(10);//error
shared_ptr<int> sp2(new int(10));//ok
②不要混用shared_ptr和普通指针。
void func(shared_ptr<int> ptr)
{
;
}//ptr离开作用域被销毁
shared_ptr<int> sp(new int(10));
func(sp);
cout<<*sp<<endl;//ok
int *p = new int(10);
func(shared_ptr<int>(p));
cout<<*p<<endl;//error,p已经变成空悬指针
③shared_ptr可用于容器中。
weak_ptr(弱智能指针):它是一种不控制指向的对象的生存周期的智能指针,它指向一个由shared_ptr管理的对象,将一个weak_ptr绑定到一个shared_ptr时,不会改变shared_ptr的引用计数,一旦最后一个指向shared_ptr的对象被销毁,对象就会被释放,即使有weak_ptr指向对象,对象也还是会被释放。
①weak_ptr是为了配合shared_ptr而引入的一种智能指针,它更像是shared_ptr的一个助手而不是智能指针,因为它不具有普通指针的行为,没有重载operator*和->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况.
②weak_ptr被设计为与shared_ptr共同工作,可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。
weak_ptr的成员函数use_count()可以观测资源的引用计数。
weak_ptr的成员函数lock()可以将weak_ptr提升为shared_ptr, 从而操作资源。
weak_ptr的成员函数lexpired()可以检测对应的shared_ptr是否释放。
循环引用(交叉引用)。
表现:
class B;
class A
{
public:
shared_ptr<B> _bptr;
};
class B
{
public:
shared_ptr<A> _aptr;
};
void main()
{
shared_ptr<A> aptr(new A());//A::_bptr引用计数为1
shared_ptr<B> bptr(new B());//B::_bptr引用计数为1
aptr->_bptr = bptr;//A::_bptr引用计数为2
bptr->_aptr = aptr;//B::_bptr引用计数为2
}//A::_bptr引用计数为1,B::_bptr引用计数为1,所以程序结束后对象不释放,造成内存泄漏
解决方法:
class A
{
public:
weak_ptr<B> _bptr;
};
class B
{
public:
weak_ptr<A> _aptr;
};
weak_ptr弱智能指针,虽然有引用计数,但实际上它并不增加计数,而是只观察对象的引用计数。所以此时对象A的引用计数只为1,对象B的引用计数也只为1。(可调用use_count()成员函数进行查看)
总结:创建对象时使用shared_ptr强智能指针指向,其余情况都使用weak_ptr弱智能指针指向