智能指针、循环引用、锁、删除器
目录智能指针使用场景的分析RAII策略和智能指针的设计思路RAII介绍四大智能指针shared_ptr常见构造(不含删除器)资源拷贝资源移动use_countshared_ptr的循环引用问题shared_ptr循环引用产生的原因unique_ptr对比shared_ptrweak_ptr解决循环引用问题加锁Ⅰ.21指针维护Ⅱ.sp1指向新对象、sp2引用计数-1(2-1)Ⅲ.sp2指针也移走指向3333333原引用计数-1(1-0)自动析构、wp指针仍维护原资源。Ⅳ.wp弱指针移动(wp内资源自动析构)wp移至sp1处引用计数1(1-2)auto sp3 wp.lock();以sp3加锁维护wp与sp1共同指向的资源引用计数1(1-2)Ⅴ.sp1移走指向新资源被加锁的sp3仍维护原资源。总图auto_ptr删除器shared_ptr删除器unique_ptr删除器两种智能指针的删除器对比编辑智能指针使用场景的分析在资源管理中指针维护指向资源涉及到的内存管理与异常分析十分重要。而在指针的new与delete(尤其是对于非内置类型的指针)时往往会不可避免的出现抛异常情况。异常类型触发场景std::bad_allocnew分配失败SIGSEGV空指针/野指针访问SIGSEGV越界/非法访问内存SIGABRT双重释放在设计了try catch语句的基础上我们可以使用其来捕获与抛出但事实情况是指针的申请与释放具备高密度性和复合性我们再使用传统的try catch来处理代码的结构性和可读性都会被极大地损害。基于以上原因设计智能指针来自动化管理指针类型。他们有unique_ptr、shared_ptr、weak_ptr及auto_ptr。RAII策略和智能指针的设计思路RAII介绍RAII(Resources Acquisition Is Initailization译作资源获取即初始化)将资源获取分配给一个对象接着控制对资源的访问。资源在生命周期内始终保持有效最后在对象析构时释放资源保证了资源的正常释放避免了资源的泄漏问题。智能指针在满足RAII的设计思路下还要方便资源的访问所以智能指针会像迭代器类一样重载operator*/operator-/operator[]等运算符方便资源访问。四大智能指针shared_ptr复习必看giteeC11引进译作共享指针。正常支持资源拷贝与资源移动底层应用了引用计数的方式实现。常见构造(不含删除器)shared_ptrT sp1(new int(999)); shared_ptrT[] sp2(new T[n]{………………}); shared_ptrT msp(move(sp1));//shared_ptr的支持移动资源拷贝资源拷贝实际上是再创建一指针与shared_ptr指向同一内容、引用计数加1资源移动移动(move)后原指针为空use_count用于返回share_ptr引用计数的数据。shared_ptr的循环引用问题前置条件一定是先外界的指针先销毁导致(*_pcount)--从2变为1 如图外界销毁完后要进行内部的销毁假设先销毁B内的内容先对sp1操作。对sp1销毁要先把sp2销毁否则导致内存泄漏。找到sp2销毁对sp1发现相同的内存泄漏风险。至此循环形成导致二者内存均无法释放。shared_ptr循环引用产生的原因shared_ptr同时在节点外与节点内使用采用引用计数的方式来简化销毁流程。所以我们应该避免使用shared_ptr设计出存在循环引用的逻辑。unique_ptrC11引进译作唯一指针。不支持拷贝只支持移动。不需要拷贝的场景就非常建议使用它。对比shared_ptrx64系统(8字节)特性unique_ptrshared_ptr资源所有权唯一独占经常共享引用计数无有拷贝语义不支持拷贝支持拷贝移动语义支持移动也支持移动内存开销8字节(单指针)16字节(双指针)性能对比极佳较佳其底层实现仅仅8字节大小与不支持拷贝 只支持移动的属性支持意味着它是独占资源的最常用维护性指针。简单使用int main() { unique_ptrint up1(new int(0)); unique_ptrint[] up2(new int[100]()); unique_ptrint[] up3(move(up2));//支持移动 //unique_ptrint[] up4(up2);不支持拷贝 //unique_ptrint[] up5 up2;//不支持拷贝构造 return 0; }unique_ptr是最常用的智能指针。weak_ptrC11引进译作弱指针。弱理解为具有指针基本性质(自动释放)的观察指针。主要作用是与shared_ptr配合时不影响引用计数其次是资源检测资源加锁控制。解决循环引用问题作为内部指针解决循环引用问题于是weak_ptr的底层简化版templatetypename T class weak_ptr { public: struct control_block { int strong_ref;//强引用计数 int weak_ref;//弱引用计数 }; private: T* _ptr; //指向对象 control_block* cb;//指向内存块 };总之weak_ptr是通过再加一层的引用计数来进行决策释放机制此机制保证了内存安全。加锁前言此加锁仅演示加锁过程。库函数expired(){return use_count 0;} 空true非空false。加锁代码下有图解int main() { // 1. 创建 shared_ptr 和 weak_ptr std::shared_ptrstring sp1(new string(111111)); std::shared_ptrstring sp2(sp1); std::weak_ptrstring wp sp1; cout --- 1. 初始状态 wp 信息 --- endl; cout wp.expired() endl; // 是否过期 cout wp.use_count() endl; // 引用计数 // 2. sp1 指向新资源 cout \n--- 2. sp1 指向 \222222\ --- endl; sp1 make_sharedstring(222222); cout wp.expired() endl; cout wp.use_count() endl; // 3. sp2 也指向新资源 cout \n--- 3. sp2 指向 \333333\ --- endl; sp2 make_sharedstring(333333); cout wp.expired() endl; cout wp.use_count() endl; // 4. wp 重新绑定到 sp1并 lock() cout \n--- 4. wp sp1调用 lock() --- endl; wp sp1; auto sp3 wp.lock(); // 提升为 shared_ptr cout wp.expired() endl; cout wp.use_count() endl; // 5. sp1 指向新资源wp 指向的资源只剩 sp3 cout \n--- 5. sp1 指向 \444444\ --- endl; sp1 make_sharedstring(444444); cout wp.expired() endl; cout wp.use_count() endl; return 0; }Ⅰ.21指针维护Ⅱ.sp1指向新对象、sp2引用计数-1(2-1)Ⅲ.sp2指针也移走指向3333333原引用计数-1(1-0)自动析构、wp指针仍维护原资源。Ⅳ.wp弱指针移动(wp内资源自动析构)wp移至sp1处引用计数1(1-2)auto sp3 wp.lock();以sp3加锁维护wp与sp1共同指向的资源引用计数1(1-2)Ⅴ.sp1移走指向新资源被加锁的sp3仍维护原资源。总图auto_ptr源于C98是失败的设计—拷贝原理拷贝/赋值时将被拷贝对象的资源转移到拷贝对象导致原指针置空。C11弃用、C17移除为什么是失败的设计与本来目的不符原目的是多指针共同维护资源但一旦使用auto_ptr就导致原对象失去资源管理能力。删除器删除器是针对于智能指针(仅限于shared_ptr与unique_ptr)在生命周期结束后调用的相应的可调用对象来(有bind返回的绑定对象、函数指针、lamda表达式、仿函数)销毁资源的方式。内置类型(new int/char等)出生命周期自动结束。非内置类型new char[] 、new string、new vector等调用的是相应非内置类型的析构函数。因此就出现了对自定义类型的讨论对于自定义类型/特殊类型我们就需要手动指定删除器来进行特定调用要求。shared_ptr删除器删除器位于非模板参数常用的有lambda表达式函数指针//shared_ptr的外部调用删除器 //函数指针 void fptr(int* a) { delete a; } //labdam表达式 auto lambda [](int* a) {delete a; }; void testShared_ptr() { std::shared_ptrint sp1(new int(999), fptr); //写fptr()认为调用函数的返回值 std::shared_ptrint sp2(new int(99), lambda); //内嵌lambda的表达式(推荐) std::shared_ptrint sp3(new int(8), [](int* a) {delete a; }); }构造函数重载删除器unique_ptr删除器void(*)(int) //函数指针指向一个返回void、接受int参数的函数 void* //数据指针指向未知类型数据的指针定义位于模板参数内//unique_ptr的模板类型删除器 //函数指针 void my_deleter(int* p) { delete p; }// my_deleter 的类型是 void(int*) //lambda表达式 auto deleter [](int* p) {delete p; }; //仿函数 struct fileDeleter { void operator()(FILE* f)const { fclose(f); } }; void testUniqueptr() { std::unique_ptrint, void(*)(int*) up1(new int(6), my_deleter); // decltype推导lambda表达式的类型 std::unique_ptrint, decltype(deleter) up2(new int(7), deleter); // 记住这样lambda的使用形式 std::unique_ptrFILE, fileDeleter up3(fopen(test.txt, r)); }两种智能指针的删除器对比// unique_ptr删除器是类型的一部分 templatetypename T, typename Deleter std::default_deleteT class unique_ptr; // shared_ptr删除器被类型擦除不体现在模板参数中 templatetypename T class shared_ptr;特性unique_ptrshared_ptr删除器类型模板参数类型擦除运行时开销通常无(若删除器为空)有(需要间接调用)编译器优化可内联难以内联