每日面经–10.14
C++的智能指针
想要弄清楚智能指针就要明白它为什么会产生,动态管理、动态数组、动态内存分配等都需要在堆上申请一块内存去保存自己的东西。你可能会觉得你自己用不到,我随便举一个栗子:比如说你下午要去买水果,但是不知道自己想买多少水果,但是由于现在必须出门,所以你要带多大的口袋是不知道的。在C++中你知道要保存一些数据到一个数组,你又想刚好数组的大小是你这些数据的大小。所以你就可以申请一个动态数组,当前面计算好了数据的大小,你这边直接赋值,比如下列程序:
如果p不申请为动态数组的话,那么会出现以下错误:
若为静态数组的话,这个n必须是常数。请注意申请了动态数组后需要用delete删除,所以delete和new是一对操作,delete[]和new[]他两都是一对cp出现,不删除的话就会有内存泄露。智能指针的作用就是它自己有delete包含,到了函数结束的时候,它自动调用自己的析构函数。
智能指针,C++11有这三个:auto_ptr,unique_ptr,shared_ptr。
砸门一个一个给大家介绍:
auto_ptr这个智能指针是最先出现的,其中有一个缺点有点难受,就是一个auto_ptr1赋值给另一个auto_ptr2后,auto_ptr1会直接没了,之后是不能对auto_ptr1进行操作的。如下图
// An highlighted block
auto_ptr<Apple> apple0(new Apple(1));
auto_ptr<Apple> apple1 = apple0;
apple0.id(); // 引发异常
unique_ptr既然前面的auto_ptr容易丢弃以往的数据,那么这个智能指针就直接是不许赋值构造和复制构造,如下图
unique_ptr<Apple> apple = new Apple(2); // 错误
unique_ptr<Apple> apple(new Apple(2)); // 正确
int main()
{
{
unique_ptr<Apple> apple2(new Apple(2));
unique_ptr<Apple> apple3 = apple2; // 编译失败
} // 超出apple2的作用域,apple2被销毁,其指针指向的对象被析构。
}
虽然不能实现赋值,但是可以将所有权给别的智能指针,如下图
int main()
{
{
unique_ptr<Apple> apple2(new Apple(2));
unique_ptr<Apple> apple3;
apple2.swap(apple3); // apple2的指针从此无效
/*
或者:
unique_ptr<Apple> apple3 = std::move(apple2); // apple2指针从此无效
*/
} // 超出apple2的作用域,apple2被销毁,其指针指向的对象被析构。
}
以上这两个智能指针都不能进行用**=进行赋值,但是shared_ptr**指针却可以,如下图所示:
#include <iostream>
#include <memory>
using namespace std;
class A
{
public:
int i;
A(int n):i(n) { };
~A() { cout << i << " " << "destructed" << endl; }
};
int main()
{
shared_ptr<A> sp1(new A(2)); //A(2)由sp1托管,
shared_ptr<A> sp2(sp1); //A(2)同时交由sp2托管
shared_ptr<A> sp3;
sp3 = sp2; //A(2)同时交由sp3托管
cout << sp1->i << "," << sp2->i <<"," << sp3->i << endl;
A * p = sp3.get(); // get返回托管的指针,p 指向 A(2)
cout << p->i << endl; //输出 2
sp1.reset(new A(3)); // reset导致托管新的指针, 此时sp1托管A(3)
sp2.reset(new A(4)); // sp2托管A(4)
cout << sp1->i << endl; //输出 3
sp3.reset(new A(5)); // sp3托管A(5),A(2)无人托管,被delete
cout << "end" << endl;
return 0;
}
程序的输出结果如下:
2,2,2
2
3
2 destructed
end
5 destructed
4 destructed
3 destructed
可以用第 14 行及第 16 行的形式让多个 sharecLptr 对象托管同一个指针。这多个 shared_ptr 对象会共享一个对共同托管的指针的“托管计数”。有 n 个 shared_ptr 对象托管同一个指针 p,则 p 的托管计数就是 n。当一个指针的托管计数减为 0 时,该指针会被释放。shared_ptr 对象消亡或托管了新的指针,都会导致其原托管指针的托管计数减 1。
第 20、21 行,shared_ptr 的 reset 成员函数可以使得对象解除对原托管指针的托管(如果有的话),并托管新的指针。原指针的托管计数会减 1。
输出的第 4 行说明,用 new 创建的动态对象 A(2) 被释放了。程序中没有写 delete 语句,而 A(2) 被释放,是因为程序的第 23 行执行后,已经没有 shared_ptr 对象托管 A(2),于是 A(2) 的托管计数变为 0。最后一个解除对 A(2) 托管的 shared_ptr 对象会释放 A(2)。
main 函数结束时,sp1、sp2、sp3 对象消亡,各自将其托管的指针的托管计数减为 0,并且释放其托管的指针,于是会有以下输出:
5 destructed
4 destructed
3 destructed
只有指向动态分配的对象的指针才能交给 shared_ptr 对象托管。将指向普通局部变量、全局变量的指针交给 shared_ptr 托管,编译时不会有问题,但程序运行时会出错,因为不能析构一个并没有指向动态分配的内存空间的指针。
注意,不能用下面的方式使得两个 shared_ptr 对象托管同一个指针:
A* p = new A(10);
shared_ptr <A> sp1(p), sp2(p);
sp1 和 sp2 并不会共享同一个对 p 的托管计数,而是各自将对 p 的托管计数都记为 1(sp2 无法知道 p 已经被 sp1 托管过)。这样,当 sp1 消亡时要析构 p,sp2 消亡时要再次析构 p,这会导致程序崩溃。