C++11标准引入了boost库中的智能指针,给C++开发时的内存管理提供了极大的方便。接下来这篇文件介绍shared_ptr/weak_ptr内部实现原理及使用细节。java
C++不像java有内存回收机制,每次程序员new出来的对象须要手动delete,流程复杂时可能会漏掉delete,致使内存泄漏。因而C++引入智能指针,可用于动态资源管理,资源即对象的管理策略。ios
C++中的shared_ptr/weak_ptr和Android的sp/wp功能相似,都为解决多线程编程中heap内存管理问题而产生的。当程序规模较小时,咱们能够手动管理new分配出来的裸指针,何时delete释放咱们本身手动控制。程序员
可是,当程序规模变大时,而且该heap内存会在各个线程/模块中进行传递和互相引用,当各个模块退出时,谁去释放?由此引入了智能指针的概念。编程
其原理能够概况为,内部经过引用计数方式,指示heap内存对象的生存周期,而智能指针变量做为一个stack变量,利用栈区变量由操做系统维护(程序员没法控制)的特色进行管理。多线程
实现细节:ide
这样的东东(咱们姑且称其为一个“类”,就像int/char/string为程序语言的内建类,咱们也能够定义本身的类来使用)须要有什么特色?函数
1.这个类内部须要有个指针,就是保护那个常常犯错的裸指针:heap内存对象指针。spa
2.这个类可以表明全部类型的指针,所以必须是模板类。操作系统
3.根据须要自动释放其指向的heap内存对象,也即当这个“智能指针类对象”释放时,其内部所包含的heap内存对象根据须要进行释放,所以这个类对象只能是一个stack区的对象(若是是heap区的,咱们还须要手动delete,而咱们但愿有个系统能帮咱们去作的东西),另一点,这个类内部还须要有个变量,用于指示内部的heap内存对象引用数量,以便决定是否释放该heap内存对象。线程
智能指针shared_ptr对象跟其它stack区对象同样有共同的特色——每次离开做用域时会自动调用析构函数进行内存回收。利用该特色,析构时检查其内部所引用的heap内存对象的引用数量进行操做:1.引用计数减一变为0时,则必须释放;2.减一后仍不为0,那么其内部的heap内存对象同时被别的智能指针引用,所以不能释放。
使用示例:
1 #include <iostream> 2 #include <memory> 3 #include <string> 4 5 using namespace std; 6 7 class Person { 8 public: 9 Person() { 10 cout<<"Person ctor"<<endl; 11 } 12 13 Person(const string &alias): name(alias) { 14 cout<<"Person ctor for "<<name.c_str()<<endl; 15 } 16 17 ~Person() { 18 cout<<"Person dtor for "<<name.c_str()<<endl; 19 } 20 21 void setFather(shared_ptr<Person> &p) { 22 father = p; 23 } 24 25 void setSon(shared_ptr<Person> &p) { 26 son = p; 27 } 28 29 void printName() { 30 cout<<"name: "<<name.c_str()<<endl; 31 } 32 33 private: 34 const string name; 35 shared_ptr<Person> father; 36 shared_ptr<Person> son; 37 }; 38 39 void test0() 40 { 41 cout<<"---------test0 normal release begin---------"<<endl; 42 43 shared_ptr<Person> sp_pf(new Person("zjz")); 44 shared_ptr<Person> sp_ps(new Person("zcx")); 45 46 cout<<"---------test0 normal release end---------\n"<<endl; 47 } 48 49 void test1() 50 { 51 cout<<"\n---------test1 no release begin---------"<<endl; 52 53 shared_ptr<Person> sp_pf(new Person("zjz")); 54 shared_ptr<Person> sp_ps(new Person("zcx")); 55 56 cout<<"addr: "<<&sp_pf<<endl; 57 cout<<"addr: "<<&sp_ps<<endl; 58 59 cout<<"111 father use_count: "<<sp_pf.use_count()<<endl; 60 cout<<"111 son use_count: "<<sp_ps.use_count()<<endl; 61 62 sp_pf->setSon(sp_ps); 63 sp_ps->setFather(sp_pf); 64 65 cout<<"222 father use_count: "<<sp_pf.use_count()<<endl; 66 cout<<"222 son use_count: "<<sp_ps.use_count()<<endl; 67 68 cout<<"---------test1 no release end---------\n"<<endl; 69 } 70 71 void test2() 72 { 73 cout<<"---------test2 release sequence begin---------"<<endl; 74 75 shared_ptr<Person> sp_pf(new Person("zjz")); 76 shared_ptr<Person> sp_ps(new Person("zcx")); 77 78 cout<<"addr: "<<&sp_pf<<endl; 79 cout<<"addr: "<<&sp_ps<<endl; 80 81 cout<<"111 father use_count: "<<sp_pf.use_count()<<endl; 82 cout<<"111 son use_count: "<<sp_ps.use_count()<<endl; 83 84 sp_pf->setSon(sp_ps); 85 //sp_ps->setFather(sp_pf); 86 87 cout<<"222 father use_count: "<<sp_pf.use_count()<<endl; 88 cout<<"222 son use_count: "<<sp_ps.use_count()<<endl; 89 90 cout<<"---------test2 release sequence end---------"<<endl; 91 } 92 93 int main(void) 94 { 95 test0(); 96 test1(); 97 test2(); 98 99 return 0; 100 }
其执行结果以下:
几点解释说明:
1.test0和test1中为何有dtor的打印?
sp_pf和sp_ps做为stack对象,当其离开做用域时自动由系统释放,所以在输出“test0 end”后test0()退出时,才会真正释放stack object。
当其释放时,检查内部引用计数为1,则能够释放引用的真正的heap内存——new Person("zjz")和new Person("zcx"),调用其析构函数。
2.释放顺序是什么?为何test0和test2中不同?
sp_pf和sp_ps做为stack对象,咱们能够回想stack对象内存管理方式——“先进后出,后进先出”,且地址变化由高地址向低地址过渡,由test1和test2中对sp_pf和sp_ps对象地址的打印信息能够验证。
那么当退出时,确定先释放sp_ps对象,再释放sp_pf对象。test1能够确认——先释放zcx,再释放zjz。
然而test2中好像正好颠倒,怎么回事呢???
答案是,仍然成立!退出test2()时,先释放sp_ps,再释放sp_pf。
在释放sp_ps时,发现其引用的这个内存对象new Person("zcx"),还同时被别人(sp_pf)引用,只能将son的引用计数减一,而不能释放,所以什么打印都没有。
在释放sp_pf时,先进入构造函数~Person(),再释放其member var。所以先有打印信息:“dtor zjz”,再释放内部的另外成员变量(对象)——son,所以后有信息“dtor zcx”。
这里面涉及到C++中另一个知识点:构造/析构前后顺序。——读者能够回顾类对象构造时,是先进入构造函数,仍是先构造内部的member。析构和构造正好颠倒。写一个demo进行了折叠,读者本身去验证。
1 #include <iostream> 2 #include <memory> 3 #include <string> 4 5 using namespace std; 6 7 class tmp { 8 public: 9 tmp() { 10 cout<<"tmp ctor"<<endl; 11 } 12 ~tmp() { 13 cout<<"tmp dtor"<<endl; 14 } 15 }; 16 17 class Person { 18 public: 19 Person() { 20 cout<<"Person ctor"<<endl; 21 } 22 23 Person(const string &alias): name(alias) { 24 cout<<"Person ctor for "<<name.c_str()<<endl; 25 } 26 27 ~Person() { 28 cout<<"Person dtor for "<<name.c_str()<<endl; 29 } 30 31 void setFather(shared_ptr<Person> &p) { 32 father = p; 33 } 34 35 void setSon(shared_ptr<Person> &p) { 36 son = p; 37 } 38 39 void printName() { 40 cout<<"name: "<<name.c_str()<<endl; 41 } 42 43 private: 44 const string name; 45 shared_ptr<Person> father; 46 shared_ptr<Person> son; 47 tmp mtmp; 48 }; 49 50 int main(void) 51 { 52 Person *pp = new Person("sequence"); 53 delete pp; 54 55 return 0; 56 }
3.test1中的好像没有释放?
是的。在setFather/Son前,refCnt=1,以后变成了2。当退出test1()时,两块heap内存new Person("zjz")和new Person("zcx")的ref减一变成1,但都因互相引用对方而没法释放。这时须要引入另一种智能指针——weak_ptr。
weak_ptr的引入:
weak_ptr是为配合shared_ptr而引入的一种智能指针来协助shared_ptr工做,它能够从一个 shared_ptr 或另外一个 weak_ptr 对象构造,它的构造和析构不会引发引用记数的增长或减小。没有重载*和->但可使用lock得到一个可用的shared_ptr对象。
为何要引入“弱引用”指针呢?
weak_ptr和shared_ptr是为解决heap对象的“全部权”而来。弱引用指针就是没有“全部权”的指针。有时候我只是想找个指向这块内存的指针,但我不想把这块内存的生命周期与这个指针关联。这种状况下,弱引用指针就表明“我指向这东西,但这东西何时释放不关我事儿……”
使用区别:
首先,不要把智能指针和祼指针的区别看得那么大,它们都是指针。所以,咱们能够把智能指针和祼指针都统称为指针,它们共同的目标是经过地址去表明资源。既然指针能表明资源,那么不可避免地会涉及资源的全部权问题。在选择具体指针类型的时候,经过问如下几个问题就能知道使用哪一种指针了。
1.指针是否须要拥有资源的全部权?
若是指针变量须要绑定资源的全部权,那么会选择unique_ptr或shared_ptr。它们能够经过RAII完成对资源生命期的自动管理。若是不须要拥有资源的全部权,那么会选择weak_ptr和raw pointer,这两种指针变量在离开做用域时不会对其所指向的资源产生任何影响。
2.若是指针拥有资源的全部权(owning pointer),那么该指针是否须要独占全部权?
独占则使用unique_ptr(人无我有,人有我丢),不然使用shared_ptr(你有我有全都有)。这一点很好理解。
3.若是不拥有资源的全部权(non-owning pointer),那么指针变量是否须要在适当的时候感知到资源的有效性?
若是须要则使用weak_ptr,它能够在适当的时候经过weak_ptr::lock()得到全部权,当拥有全部权后即可以得知资源的有效性。