定义:有的时候我们申请了内存空间,但是在离开作用域的时候(由于某种原因)可能未及时释放,从而会造成内存泄漏。智能指针就是为了解决这种情况而设计的。
看下面这个简单的例子:
int main() { try{ int *tmp = new int(10); throw("throw exception");//此处抛出异常 delete tmp;//此处代码未执行 }catch(...){ cout<<"Wrong!!"<<endl; } return 0; }
因为还没有执行到delete tmp处就抛出了异常,会造成内存泄漏。对于普通的局部变量(非静态局部变量),当离开它的作用域时,操作系统会自动将其释放的;并且我们也知道对于类对象在释放的时候是会自动调用该类的析构函数。
于是我们就想:如果是int *tmp不是一个普通的指针变量,而是一个类对象的话,并且在类的析构函数中实现了释放动态内存的步骤;那么只要该指针变量一退出作用域时就会调用析构函数,达到了释放动态内存的目的。这就是智能指针的思想。
C++中的智能指针:
C++ STL为我们提供了四种智能指针:auto_ptr、unique_ptr、shared_ptr和weak_ptr;其中auto_ptr是C++98提供,在C++11中建议摒弃不用,至于原因,后面会讲到;而unique_ptr、shared_ptr和weak_ptr则是随着C++11的到来而加入到STL中,下面对其进行说明。(使用STL提供的智能指针需要头文件memory)
(1)auto_ptr、unique_ptr 和 shared_ptr的使用
使用方法(另外两个类似):
auto_ptr<int> p1(new int(10)); cout<<*p1<<endl;
来看一个例子:
Node *p1 = new Node; Node *p2; p2 = p1;这里假设p2 = p1执行的是浅拷贝(其中Node是一个类)。这样p2和p1指向同一块内存,那么在析构的时候,那块内存会被析构两次。看一些智能指针如何解决这种问题:
建立所有权(ownership)概念。对于特定的对象,同一时刻只能有一个智能指针可拥有, 比如说当智能指针A指向对象x,当执行完B=A后,原来的指针A就失去了对x的所有权,这样只有拥有对象的智能指针的构造函数会删除该对象,unique_ptr 和 auto_ptr就是采用这种策略。
创建智能更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数。例如,赋值时,计数将加1,而指针过期时,计数将减1,。当减为0时才调用delete。这是shared_ptr采用的策略。
接下来说为什么不建议使用auto_ptr :
auto_ptr<int> p1(new int(10)); auto_ptr<int> p2; p2 = p1; cout<<*p1<<endl;
上述代码会导致程序崩溃,这里当执行完赋值语句p2 = p1后,再去访问p1时程序崩溃了。原因就是因为赋值语句p2 = p1使得对象的所有权从p1转让给p2了,p1已经变为空指针了,再去访问p1当然会出错了。
接下来看一下unique_ptr和shared_ptr遇到这种情况会怎么样?
unique_ptr<int> p1(new int(10)); unique_ptr<int> p2; p2 = p1; cout<<*p1<<endl;
如果改成上面那样在编译的时候就会提示错误,程序不会等到运行阶段崩溃。
shared_ptr<int> p1(new int(10)); shared_ptr<int> p2; p2 = p1; cout<<*p1<<endl;如果改成上面那样,程序运行正常,因为shared_ptr采用引用计数,当执行完赋值语句p2 = p1后,p1和p2指向同一块内存,只不过在释放空间时,需要判断引用计数值的大小,因此不会出现多次删除一个对象的错误。
(2)weak_ptr 的使用
weak_ptr 是为了配合 shared_ptr 而引入的一种智能指针,它更像是 shared_ptr 的一个助手而不是智能指针,因为它不具有普通指针的行为,没有重载 operator* 和 -> ,它的最大作用在于协助 shared_ptr 工作,像旁观者那样观测资源的使用情况.
看如下代码:
#include <iostream> #include <memory> using namespace std; class Node { public: shared_ptr<Node> _prev; shared_ptr<Node> _next; ~Node() { cout << "delete :" <<this<< endl; } }; int main() { shared_ptr<Node> cur(new(Node)); shared_ptr<Node> next(new(Node)); cur->_next = next; next->_prev = cur; return 0; }
程序运行结果是什么也没有输出,这样两个节点都没有释放,因为cur的释放要提前释放next,而next的释放需要提前释放cur,这样就导致谁都释放不了。
解决方法:
#include <iostream> #include <memory> using namespace std; class Node { public: ~Node() { cout << "delete:" << this << endl; } public: weak_ptr<Node> _prev; weak_ptr<Node> _next; }; void print() { shared_ptr<Node> cur(new Node()); shared_ptr<Node> next(new Node()); cout << "连接前:" << endl; cout << "cur:" << cur.use_count() << endl;//引用计数 cout << "next:" << next.use_count() << endl; cur->_next = next; next->_prev = cur; cout << "连接后:" << endl; cout << "cur:" << cur.use_count() << endl; cout << "next:" << next.use_count() << endl; } int main(){ print(); }
shared_ptr 存在循环引用的问题,使用weak_ptr可以用来避免循环引用。但是weak_ptr对象引用资源时不会增加引用计数,无法知道资源会不会被突然释放,所以无法通过weak_ptr访问资源。在访问资源时weak_ptr必须先转化为shared_ptr。
(3)如何选择智能指针?
建议:
如果程序中要使用多个指向同一个对象的指针,那么应该使用shared_ptr;比如说现在有一个包含指针的STL容器,现在用某个支持复制和赋值操作STL算法去操作该容器的指针元素,那么就应该用shared_ptr。不能用unique_ptr(编译器报错)和auto_ptr(行为不确定)。
如果程序中不需要使用多个指向同一个对象的指针,则可使用unique_ptr;如果函数使用new分配内存,并返回指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。这样,所有权转让给接受返回值的unique_ptr,而该智能指针将负责调用delete。
在满足unique_ptr要求的条件时,也可使用auto_ptr,但unique_ptr是更好的选择。
References :
http://www.cnblogs.com/Lynn-Zhang/p/5699983.html
http://blog.51cto.com/12951882/2088711
https://www.cnblogs.com/wxquare/p/4759020.html
https://www.cnblogs.com/lanxuezaipiao/p/4132096.html
https://blog.csdn.net/hackbuteer1/article/details/7561235
https://blog.csdn.net/zsc_976529378/article/details/52250597
https://mp.weixin.qq.com/s/fM9fM1UhLhFWHJyKhFyhrg