在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
没有智能指针的c++时代,对堆内存的管理就是简单的new delete。
内部大概实现: //大概长这个样子(化简版) template<class T> class auto_ptr{ T* ptr; }; 示例用法: void runGame(){ std::auto_ptr<Monster> monster1(new Monster());//monster1 指向 一个怪物 monster1->doSomething();//怪物做某种事 } //runGame函数执行完时,monster1被释放,然后它的析构函数也把指向的一个怪物释放了,要死带着一起死(o_o)
复制auto_ptr对象时,把指针指传给复制出来的对象,原有对象的指针成员随后重置为nullptr。 void runGame(){ std::auto_ptr<Monster> monster1(new Monster());//monster1 指向 一个怪物 monster1->doSomething();//怪物做某种事 std::auto_ptr<Monster> monster2 = monster1;//转移指针 monster2->doSomething();//怪物做某种事 monster1->doSomething();//Oops!monster1智能指针指向了nullptr,运行期崩溃。 }
注意: 虽然它是c++11以前的最原始的智能指针,但是在c++11中已经被弃用(使用的话会被警告)了。
unique_ptr(一种强引用指针)“它是我的所有物,你们都不能碰它!”——鲁迅
内部大概实现: 它其实算是auto_ptr的翻版(都是独占资源的指针,内部实现也基本差不多). 但是unique_ptr的名字能更好的体现它的语义,而且在语法上比auto_ptr更安全(尝试复制unique_ptr时会编译期出错,而auto_ptr能通过编译期从而在运行期埋下出错的隐患) 假如你真的需要转移所有权(独占权),那么你就需要用std::move(std::unique_ptr对象)语法,尽管转移所有权后 还是有可能出现原有指针调用(调用就崩溃)的情况。
示例用法: void runGame(){ std::unique_ptr<Monster> monster1(new Monster());//monster1 指向 一个怪物 std::unique_ptr<Monster> monster2 = monster1;//Error!编译期出错,不允许复制指针指向同一个资源。 std::unique_ptr<Monster> monster3 = std::move(monster1);//转移所有权给monster3. monster1->doSomething();//Oops!monster1指向nullptr,运行期崩溃 } (额外:boost库的boost::scoped_ptr也是一个独占性智能指针,但是它不允许转移所有权,从始而终都只对一个资源负责,它更安全谨慎,但是应用的范围也更狭窄。)
shared_ptr(一种强引用指针)“它是我们(shared_ptr)的,也是你们(weak_ptr)的,但实质还是我们的”——鲁迅 多个shared_ptr指向同一处资源,当所有shared_ptr都全部释放时,该处资源才释放。
内部大概实现: (用原始指针构造时,会new一个SharedPtrControlBlock出来作为计数存放的地方,然后用指针指向它,计数加减都通过SharedPtrControlBlock指针间接操作。) //shared计数放在这个结构体里面,实际上结构体里还应该有另一个weak计数。下文介绍weak_ptr时会解释。 struct SharedPtrControlBlock{ int shared_count; }; //大概长这个样子(化简版) template<class T> class shared_ptr{ T* ptr; SharedPtrControlBlock* count; };
每次复制,多一个共享同处资源的shared_ptr时,计数+1。每次释放shared_ptr时,计数-1。
示例用法: void runGame(){ std::shared_ptr<Monster> monster1(new Monster()); //计数加到1
缺陷:模型循环依赖(互相引用或环引用)时,计数会不正常 假如有这么一个怪物模型,它有2个亲人关系 class Monster{ std::shared_ptr<Monster> m_father; std::shared_ptr<Monster> m_son; public: void setFather(std::shared_ptr<Monster>& father);//实现细节懒得写了 void setSon(std::shared_ptr<Monster>& son); //懒 ~Monster(){std::cout << "A monster die!";} //析构时发出死亡的悲鸣 }; 然后执行下面函数 void runGame(){ std::shared_ptr<Monster> father = new Monster(); std::shared_ptr<Monster> son = new Monster(); father->setSon(son); son->setFather(father); } 猜猜执行完runGame()函数后,这对怪物父子能正确释放(发出死亡的悲鸣)吗? 那么我们来模拟一遍(自行脑海模拟一遍最好),函数退出时栈的shared_ptr对象陆续释放后的情形: son智能指针退出栈: father智能指针退出栈: 函数结束:所有计数都没有变0,也就是说中途没有释放任何堆对象。 为了解决这一缺陷的存在,弱引用指针weak_ptr的出现很有必要。
weak_ptr(一种弱引用指针)“它是我们(weak_ptr)的,也是你们(shared_ptr)的,但实质还是你们的”——鲁迅 weak_ptr是为了辅助shared_ptr的存在,它只提供了对管理对象的一个访问手段,同时也可以实时动态地知道指向的对象是否存活。 (只有某个对象的访问权,而没有它的生命控制权 即是 弱引用,所以weak_ptr是一种弱引用型指针)
内部大概实现: 计数区域(SharedPtrControlBlock)结构体引进新的int变量weak_count,来作为弱引用计数。 被管理资源的释放只取决于shared计数,当shared计数为0,才会释放被管理资源, 但是计数区域的释放却取决于shared计数和weak计数,当两者均为0时,才会释放计数区域。 //shared引用计数和weak引用计数 //之前的计数区域实际最终应该长这个样子 struct SharedPtrControlBlock{ int shared_count; int weak_count; }; //大概长这个样子(化简版) template<class T> class weak_ptr{ T* ptr; SharedPtrControlBlock* count; };
针对空悬指针问题: 空悬指针问题是指:无法知道指针指向的堆内存是否已经释放。 得益于引入的weak_count,weak_ptr指针可以使计数区域的生命周期受weak_ptr控制, 从而能使weak_ptr获取 被管理资源的shared计数,从而判断被管理对象是否已被释放。(可以实时动态地知道指向的对象是否被释放,从而有效解决空悬指针问题) 它的成员函数expired()就是判断指向的对象是否存活。
针对循环引用问题: class Monster{ //尽管父子可以互相访问,但是彼此都是独立的个体,无论是谁都不应该拥有另一个人的所有权。 std::weak_ptr<Monster> m_father; //所以都把shared_ptr换成了weak_ptr std::weak_ptr<Monster> m_son; //同上 public: void setFather(std::shared_ptr<Monster>& father); //实现细节懒得写了 void setSon(std::shared_ptr<Monster>& son); //懒 ~Monster(){std::cout << "A monster die!";} //析构时发出死亡的悲鸣 }; 然后执行下面的函数 void runGame(){ std::shared_ptr<Monster> father(new Monster()); std::shared_ptr<Monster> son(new Monster()); father->setSon(son); son->setFather(father); } 那么我们再来模拟一遍,函数退出时栈的shared_ptr对象陆续释放后的情形: son智能指针退出栈: father智能指针退出栈: 函数结束,释放行为正确。 (可以说,当生命控制权没有彼此互相掌握时,才能正确解决循环引用问题,而弱引用的使用可以使生命控制权互相掌握的情况消失) 此外: void runGame(){ std::shared_ptr<Monster> monster1(new Monster()); std::weak_ptr<Monster> r_monster1 = monster1; r_monster1->doSomething();//Error! 编译器出错!weak_ptr没有重载* 和 -> ,无法直接当指针用 std::shared_ptr<Monster> s_monster1 = r_monster1.lock();//OK!可以通过weak_ptr的lock方法获得shared_ptr。
总结(语义)1、不要使用std::auto_ptr(已经在C++11或以上标准中弃用) 2、当你需要一个独占资源所有权(访问权+生命控制权)的指针,且不允许任何外界访问,使用std::unique_ptr 3、当你需要一个共享资源所有权(访问权+生命控制权)的指针,使用std::shared_ptr 4、当你需要一个能访问资源,但不控制其生命周期的指针,使用std::weak_ptr
推荐用法: 逻辑上,大部分模型的生命在直观上总是受某一样东西直接控制而不是多样东西共同控制。 |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论