Hello大家好 很高兴与大家见面 给生活添点快乐开始今天的编程之路。我的博客:但愿.我的专栏:C语言、题目精讲、算法与数据结构、C欢迎点赞关注目录前言一RAII和智能指针的设计思路1.1RAII1.2 智能指针1.2.1智能指针的概念1.2.2智能指针的特点作用1.2.3实现智能指针中的难点拷贝构造二C标准库中的智能指针2.1 C标准库中的智能指针2.2为什么又shared_ptr还要unique_ptr2.3C标准库中的智能指针的使用2.4智能指针的原理2.4.1auto_ptr的原理2.4.2 unique_ptr的原理2.4.3 shared_ptr的原理2.4.3.1 简单的shared_ptr的实现2.4.3.2 allocate_sharedshared_ptr的优化2.4.3.3shared_ptr中删除器的实现(unique_ptr一样)2.4.3.3.1库中shared_ptr怎么实现删除器2.4.3.3.2自己实现shared_ptr的删除器2.4.4 weak_ptr2.4.4.1 weak_ptr由来和用途2.4.4.2shared_ptr的 循环引用问题2.4.4.2 .1循环引用问题怎么形成的2.4.4.2 2循环引用问题怎么解决三 C11和boost中智能指针的关系前言在异常中我们已经讲过由于异常引起的运行逻辑的变化而导致一些资源难以管理处理方法是交给一个对象管理利用这个对象的生命周期进行管理而这个对象就是智能指针。一RAII和智能指针的设计思路1.1RAIIRAII(Resource Acquisition Is Initialization)资源请求立即初始化是一种利用对象生命周期对资源进行管理的简单技术。他是⼀种管理资源的类的设计思想本质是⼀种利⽤对象⽣命周期来管理获取到的动态资源避免资源泄漏这⾥的资源可以是内存、⽂件指针、⽹络连接、互斥锁等等(即我们获取到资源后不用自己管理由于异常等等原因我们很难自己实现管理[自己delete、free]而是把这些资源委托给一个自定义类型对象管理利用这个对象的生命周期进行管理,注意定义成一个类模板是为了实现任意类型都可以调用。•利用对象是生命周期管理RAII在获取资源时把资源委托给⼀个对象接着控制对资源的访问资源在对象的⽣命周期内始终保持有效最后在对象析构的时候释放资源这样保障了资源的正常释放避免资源泄漏问题。•总结 取到资源立即初始化是初始化的本质是调用对象的构造函数也就是说获取到资源后不用自己管理马上委托给一个对象管(构造一个对象)这个对象出了作用域就会自动析构不用我们显示的析构。•RAII的思想对资源进行管理的好处无须显示释放资源对象出了其作用域就会自动调用析构函数释放资源。例如在前面的程序中因为抛异常导致资源难以释放而采用这种方法就很好。对象所须的资源在生命周期内始终保持有效。【示例】简单的RAII这里还是与一个除法函数的调用链作为实例//简单的RAII templateclass T class SmartPtr { public: //RAII SmartPtr(T* ptr) :_ptr(ptr) { } ~SmartPtr() { cout delete[]: _ptr endl; delete[] _ptr; } private: T* _ptr; }; double Divide(int a, int b) { // 当b 0时抛出异常 if (b 0) { throw Divide by zero condition!; } else { return (double)a / (double)b; } } void Func() { // 这里可以看到如果发生除0错误抛出异常另外下面的array和array2没有得到释放。 // 所以这里捕获异常后并不处理异常异常还是交给外面处理这里捕获了再重新抛出去。 // 但是如果array2new的时候抛异常呢就还需要套一层捕获释放逻辑这里更好解决方案 // 是智能指针否则代码太戳了 int* array1 new int[10]; // try int* array2 new int[10]; // 抛异常呢 try { int len, time; cin len time; cout Divide(len, time) endl; } catch (...) { cout delete [] array1 endl; cout delete [] array2 endl; delete[] array1; delete[] array2; throw; // 异常重新抛出捕获到什么抛出什么 } // ... cout delete [] array1 endl; delete[] array1; cout delete [] array2 endl; delete[] array2; } void Func() { SmartPtrint sp1(new int[10]); SmartPtrint sp2(new int[10]); // 抛异常呢 SmartPtrint sp3(sp1); for (size_t i 0; i 10; i) { sp1[i] sp2[i] i; } int len, time; cin len time; cout Divide(len, time) endl; } int main() { try { Func(); } catch (const char* errmsg) { cout errmsg endl; } catch (const exception e) { cout e.what() endl; } catch (...) { cout 未知异常 endl; } return 0; }1.2 智能指针1.2.1智能指针的概念智能指针用于管理资源对象既然你把这个对象给智能指针管理那避免不了访问这个对象你的东西那怎么访问呢这里就要重载*、-、[ ]等运算符(看情况重载)重载*、-是为了可以和普通指针一样进行访问和复合类型的访问重载[ ]是方便数组类似的访问。结合RAII我们可以知道智能指针的基础是;•RAII的设计思路方便管理资源•运算符的重载方便我们访问对象的资源。还有一些地方是传值返回此时必然要调用拷贝构造这也是智能指针的难点简单的智能指针式很简单的如果不实现拷贝构造编译器默认生成的拷贝构造是一个浅拷贝(会导致两个对象指向同一块资源从而导致资源释放两次)1.2.2智能指针的特点作用【特点】智能指针的特点是其内部成员变量只有一个指针变量就可以了在构造时获取到资源后可以委托给我帮你代管实现自动释放(借助对象的析构函数析构函数会自动调用)此时不管出现什么因为(异常)只要出了其作用域都会自动析构。即利用构造和析构函数把资源委托给智能指针在智能指针这个对象的生命周期内这个都有效无论是否发生意外只要出了其作用域都会自动析构。【作用】由于智能指针这个对象的生命周期内这个都有效无论是否发生意外只要出了其作用域都会自动析构。所以这是一个很可靠的方法所以其在C中式非常重要的(因为不把资源给其管理自己管理很容易出问题只有一出现问题就是内存陷漏内存陷漏多了程序就终止了【很重要面试常问】。1.2.3实现智能指针中的难点拷贝构造这时候大家一定会想自己实现一个深拷贝不就行了吗这里有个问题智能指针它是一个代管即operator*、-、[ ]等都是模拟指针的行为(智能指针是一个伪指针。由于智能指针不拥有资源它只是代管资源所以把一个智能指针拷贝给另一个智能指针的本质是希望我们两个一起管理这个资源(即指向同一块资源所以进行深拷贝是不行的)。那怎么解决这个问题那我们来看一看库中怎么解决这个问题。【实例在RAII的基础上加上访问资源的运算符重载即可】templateclass T class SmartPtr { public: // RAII SmartPtr(T* ptr) :_ptr(ptr) {} ~SmartPtr() { cout delete[]: _ptr endl; delete[] _ptr; } // 重载运算符模拟指针的行为方便访问资源 T operator*() { return *_ptr; } T* operator-() { return _ptr; } T operator[](size_t i) { return _ptr[i]; } private: T* _ptr; };二C标准库中的智能指针2.1 C标准库中的智能指针•C标准库中的智能指针都在memory这个头⽂件下⾯我们包含memory就可以是使⽤了智能指针有好⼏种除了weak_ptr他们都符合RAII和像指针⼀样访问的⾏为原理上⽽⾔主要是解决智能指针拷⻉时的思路不同。•auto_ptr是C98时设计出来的智能指针他的特点是拷⻉时把被拷⻉对象的资源的管理权转移给拷⻉对象这是⼀个⾮常糟糕的设计因为他会到被拷⻉对象悬空访问报错的问题。auto_ptr:这个智能指针有两个问题1这种方式并不那么复合我们的要求我们进行拷贝后两个智能这种应该指向同一块资源共同管理。2由于进行了管理权的转移会形成悬空问题如果此时有人不知道对其进行访问麻烦就大了。【这种方法很不好所以现在好少人会使用这个甚至现在又很多公司已经禁止使用这个智能指针】为了弥补这个的不足C11引入新的智能指针。•unique_ptr是C11设计出来的智能指针他的名字翻译出来是唯⼀指针他的特点的不⽀持拷⻉只⽀持移动。如果不需要拷⻉的场景就⾮常建议使⽤他(右值、move(左值)。unique_ptr本质和auto_ptr一样只支持资源的转移(不就和auto_ptr一样)但是要注意这里是对右值(一些临时对象等等)也没人用这样一想也没有问题。这里这个行为是知道的(auto_ptr是不知道的)虽然和auto_ptr一样会导致悬空问题的发生但是性质是不一样的这里是自己知道的自己要为自己负责(语法行为不是库的问题)。•shared_ptr是C11设计出来的智能指针他的名字翻译出来是共享指针他的特点是⽀持拷⻉也⽀持移动。如果需要拷⻉的场景就需要使⽤他了。底层是⽤引⽤计数的⽅式实现的。•weak_ptr是C11设计出来的智能指针他的名字翻译出来是弱指针他完全不同于上⾯的智能指针他不⽀持RAII也就意味着不能⽤它直接管理资源weak_ptr的产⽣本质是要解决shared_ptr的⼀个循环引⽤导致内存泄漏的问题。•智能指针析构时默认是进⾏delete释放资源这也就意味着如果不是new出来的资源交给智能指针管理析构时就会崩溃。智能指针⽀持在构造时给⼀个删除器所谓删除器本质就是⼀个可调⽤对象这个可调⽤对象中实现你想要的释放资源的⽅式当构造智能指针时给了定制的删除器在智能指针析构时就会调⽤删除器去释放资源。因为new[]经常使⽤所以为了简洁⼀点unique_ptr和shared_ptr都特化了⼀份[]的版本使⽤时unique_ptrDate[] up1(newDate[5]);shared_ptrDate[] sp1(new Date[5]);就可以管理new []的资源。•template class T, class... Args shared_ptrT make_shared(Args... args);•shared_ptr除了⽀持⽤指向资源的指针构造还⽀持make_shared⽤初始化资源对象的值直接构造。•shared_ptr和unique_ptr都⽀持了operator bool的类型转换如果智能指针对象是⼀个空对象没有管理资源则返回false否则返回true意味着我们可以直接把智能指针对象给if判断是否为空。•shared_ptr和unique_ptr都得构造函数都使⽤explicit 修饰防⽌普通指针隐式类型转换成智能指针对象。【unique_ptr、shared_ptr的接口两者的接口差不多可能由于特性的区别有一点差异】相同接口)•get接口获取底层指针• relase接口手动释放置成空(想提前释放资源可以使用)• reset接口把之前的释放掉返回一个新的指针• relase和reset接口的区别relase)主动释放reset)把旧的释放返回一个新的•operator bool接口这个接口涉及到类型的转换 首先类型的转换要有一定的联系(例如内置类型自定义类型可以转换成另外一个类型而其他类型[内置类型/自定义类型都可以]转换成自定义类型要依靠于对应的构造函数)。涉及这个接口的意义自定义类型转换为内置类型 由operator 内置类型( ) 支持。示例自定义联系转换为bool自定义类型转换为bool类型这个一定不是通过构造函数实现的我们不可能给bool类型写一个构造函数(也写不出来)但是很多地方还是要支持自定义类型转换内置类型的(例如在智能指针中就有一个检查这个智能指针是否为空的接口即有没有管理资源)因为这里不是上面两种转换情况所以这里设计这个接口但是库中的写法很奇怪(括号的位置)因为这个没有返回值比较特殊返回值就是bool,本来是bool operator(,而强转是(bool) x 这不就变成了仿函数所以这里是没办法只能这样。接口的不同)shared_ptr的构造函数中一些使用了explici修饰(防止编译器中间不优化产生临时对象)因为shared_ptr支持拷贝如果此时发生拷贝没有优化就麻烦了。shared_ptr重载了所以其支持直接打印shared_ptr因为其有一个特殊的对象引用计数所以其一定增加了一些引用计数相关的接口unique接口判断资源是不是只有一个对象管理(即引用计数是不是1)个人感觉这个接口是多余的因为其还有use_count接口获取对象的引用计数。2.2为什么又shared_ptr还要unique_ptr因为shared_ptr中又引用计数所以我们要维护底层的引用计数、并且其正常拷贝这两个都是有代价的即shared_ptr有一定的性能消耗。如果不需要拷贝为啥还用shared_ptr用unique_ptr不更好这就是其存在的意义(如果不用拷贝用两者的可以都是用unique_ptr性能更优)。2.3C标准库中的智能指针的使用1auto_ptr由于其底层是资源管理权的转移不用使用原对象进行访问即可struct Date { int _year; int _month; int _day; Date(int year 1, int month 1, int day 1) :_year(year) , _month(month) , _day(day) { } ~Date() { cout ~Date() endl; } }; int main() { auto_ptrDate ap1(new Date(2025, 6, 6)); // 拷贝时管理权转移就导致ap1悬空 auto_ptrDate ap2(ap1); // 悬空访问 ap1-_year;//由于发生资源管理权的转移即原对象指向空(发生悬空,此时就不能使用原对象进行访问 return 0; }auto_ptr运行结果2 unique_ptr取底层只支持移动不支持构造底层一定把构造函数好拷贝赋值设置为delete所以在所有一定要注意其不支持拷贝在初始化时不能使用{ }初始化因为{ }初始化走的过程是构造在拷贝构造编译器优化为直接构造而unique_ptr是不支持构造的所以这种方法初始化时不行的。int main() { //unique_ptrDate up1 { 2025, 6, 6 }; //不支持{ }初始化其走的过程会使用到构造而unique_ptr不支持构造 unique_ptrDate up1(new Date(2025, 6, 6)); // 不支持拷贝 //unique_ptrDate up2(up1); // 可以移动up1也不能使用 //unique_ptrDate up2(move(up1)); cout up1.get() endl; //up1.release(); //up1.reset(new Date); // operator bool() //if (up1.operator bool()) if (up1) { cout up1 不为空 endl; } else { cout up1 为空 endl; } return 0; }3 shared_ptrint main() { shared_ptrDate sp1(new Date(2025, 6, 6)); // 支持拷贝 shared_ptrDate sp2(sp1); cout sp1 endl; cout sp2 endl; cout sp1.use_count() endl; auto sp3 make_sharedDate(2025, 6, 7); return 0; }2.4智能指针的原理2.4.1auto_ptr的原理auto_ptr的原理为管理权转移通过auto_ptr对动态分配内存资源进行管理但如果在使用过程中发生拷贝、赋值等行为时直接将对资源的管理权转移给新对象所以其实现还是很简单的在拷贝构造对象和拷贝赋值中进行管理权转移即可。【实现代码】templateclass T class auto_ptr { public: auto_ptr(T* ptr) :_ptr(ptr) { } auto_ptr(auto_ptrT sp) :_ptr(sp._ptr) { // 管理权转移 sp._ptr nullptr; } auto_ptrT operator(auto_ptrT ap) { // 检测是否为⾃⼰给⾃⼰赋值 if (this ! ap) { // 释放当前对象中资源 if (_ptr) delete _ptr; // 转移ap中资源到当前对象中 _ptr ap._ptr; ap._ptr NULL; } return *this; } ~auto_ptr() { if (_ptr) { cout delete: _ptr endl; delete _ptr; } } // 像指针⼀样使⽤ T operator*() { return *_ptr; } T* operator-() { return _ptr; } private: T* _ptr; };2.4.2 unique_ptr的原理因为其底层不支持构造只支持移动所以其底层一定是将其构造函数和拷贝赋值函数设置为delete。templateclass T class unique_ptr { public: explicit unique_ptr(T* ptr) :_ptr(ptr) { } ~unique_ptr() { if (_ptr) { cout delete: _ptr endl; delete _ptr; } } // 像指针⼀样使⽤ T operator*() { return *_ptr; } T* operator-() { return _ptr; } //底层将构造函数和赋值拷贝构造函数置为dekete unique_ptr(const unique_ptrT sp) delete; unique_ptrT operator(const unique_ptrT sp) delete; unique_ptr(unique_ptrT sp) :_ptr(sp._ptr) { sp._ptr nullptr; } unique_ptrT operator(unique_ptrT sp) { delete _ptr; _ptr sp._ptr; sp._ptr nullptr; } private: T* _ptr; };2.4.3 shared_ptr的原理shared_ptr底层就不一样了其成员变量还有用于储存引用计数的变量shared_ptr通过引用计数的方式实现多个对象间资源共享每一个shared_ptr对象内部会维护一把计数器用来记录该资源被几个对象共享。在shared_ptr对象构造时引用计数加1析构时引用计数减1。但需要主要的是析构时只有引用计数减为0也意味着自己是这块资源的最后使用者此时才能对所管理的资源进行释放但对象构造和析构时都需要对引用计数进行操作。此时多执行并发访问时可能会导致数据不一致问题所以我们需要对计数器进行加锁2.4.3.1 简单的shared_ptr的实现【那怎么实现引用计数了】方法一(前面STL中的思路 不行)给其增加一个记录引用计数的成员变量例如一个shared_ptr对象sp1此时sp1的引用计数是1如果此时执行shared_ptr sp2(sp1)此时sp2和sp1的引用计数对于二如果此时sp1析构sp1的引用计数变成1但是sp2的引用计数还是2(上面分析的原理应该此时sp2的引用计数应该是1),所以这种方法不行。即不能各自有自己的引用计数。方法二 (方法一中说了不能各自有自己的引用计数这里不就可以使用static静态变量储存这种方法也不行)虽然使用static静态变量储存此时的引用计数就是同一个引用计数了但是其还会引发其他问题例如例如一个shared_ptr对象sp1此时sp1的引用计数是1如果此时执行shared_ptr sp2(sp1)此时sp2和sp1的引用计数对于二如果此时还有一个shared_ptr对象sp3由于此时储存引用计数的变量是静态变量所以sp3的引用计数也等于2(变量应该是1)所以这种方法也不行。其实我们需要的是应该资源配一个引用计数这个资源就给智能指针管理引用计数。使用静态的成员变量储存其属于这个类型的属于对象共享一个引用计数而我们需要的是资源和引用计数配对在一起。方法三可行 (有一个资源就构造一个引用计数有一个资源就构造一个引用计数如果有多个智能指针指向同一个资源就指向同一个引用计数所以引用计数去堆上开(new.【怎么解决shared_ptr的难点拷贝】上面的引用计数的设置就是为拷贝而写要注意的是要指向同一个资源把你的指针引用计数给我引用计数在即可。【怎么实现shared_ptr的拷贝赋值】sp1sp3【sp1和sp1都是shared_ptr对象】方法一(不行)一上来肯定增加this的对象给的对象(这样不就和上面的拷贝一样吗),这样是不行的拷贝是一个对象初始化另一个对象什么这个对象什么也没有此时直接把资源和引用计数直接给我是没有问题的。赋值将一个已经的对象拷贝给了一个对象即两个存在的对象可能两个都有自己的资源和引用计数如果还用这种实现就会导致sp1资源丢了方法二(不行)此时有人说sp1是已经存在的对象我们先把sp1释放不就好了吗我们来进行下面操作sp1sp2(sp1sp3sp1sp3【sp1、2、3都是shared_ptr对象】如果此时sp1被释放了就会出问题因为sp1,sp2之前是指向同一块资源现在sp2还管着呢如果此时采用这种方法直接释放sp1sp1释放sp2就完了(即一个东西是我们两个人一起买的现在你要用新的你就直接把旧的送人不管我死活)方法三在赋值之前要判断赋值对象(sp1sp3中的sp3指向的资源是否还有别的智能指针在管理通过引用计数就可以知道引用计数1就只有自己)。如果不只有自己管理就将引用计数--即可。还要防止自己给自己赋值而自己给自己赋值有两种第一种直接的sp1sp1第二种间接的sp1sp3;sp1sp3那怎么同时处理这种了这里就不通过智能指针进行判断了而是判断是否指向同一块资源(因为这里同一个对象会指向同一块资源)。【实现代码】template class T class Shared_ptr { public: Shared_ptr(T* ptr nullptr) :_ptr(ptr), _Pcount(new std::atomicint(1)) { } Shared_ptr(const Shared_ptrT sp) :_ptr(sp._ptr), _Pcount(sp._Pcount) { (*_Pcount); } Shared_ptrT operator(const Shared_ptrT sp) { if (_ptr ! sp._ptr) // 防止直接或间接和自己赋值指向同一块空间 { release(); // 在赋值前将自身引用计数-- _ptr sp._ptr; _Pcount sp._Pcount; (*_Pcount); return *this; } } int use_count() const { return _Pcount-load(); } T* get() const { return _ptr; } // const 防止权限放大 T* operator-() { return _ptr; } T operator*() { return *_ptr; } ~Shared_ptr() { release(); } private: void release() { if (--(*_Pcount) 0) { delete _ptr; delete _Pcount; } } private: T* _ptr; std::atomicint* _Pcount; // 不能设置为静态static int count };2.4.3.2 allocate_sharedshared_ptr的优化前面已经讲了shared_ptr有不足shared_ptr中指向的资源就不说了但是其还开辟了引用计数变量如果此时大量使用(例如在容器中有很多数据每个数据用一个智能指针管理)此时就会有小块内存性能问题和内存的碎片化这两个问题C为解决这个问题而引入allocate_shared2.4.3.3shared_ptr中删除器的实现(unique_ptr一样)2.4.3.3.1库中shared_ptr怎么实现删除器【为什么要实现定制删除器】因为其底层的释放方式是delete如果这里开辟空间用new没问题但是如果使用malloc开空间呢?如果还使用delete释放就不行了此时就有使用定制删除器(规定释放方法)。记住【实现方法三种(仿函数函数指针lambda)一般不用函数指针麻烦】从库中可以知道shared_ptr是一个函数模板而unique_ptr使用在使用时的情况时不一样的。shared_ptr可以在时传一个删除器(一个可调用对象)D del(del即delete)即底层还是delete只是传了删除器D就会把那个资源指针传给D(可调用对象进行释放)。对于shared_ptr和删除器相关的都是函数模板可以自动推导(即可以在函数模板时传也可以在构造时传删除器)而对于unique_ptr是一个类模板(必须在函数时传传对应的类型所以一般使用仿函数或者lambda实现(不用函数指针很麻烦)但是要先定义一个lambda对象在用decltype获得到其对应的类型传入但是lambda时不支持构造等等所以其无法推导还要在后面函数中加对应的对象【总结】要释放这个东西的时候要定制删除器(仿函数lambda,最好用shared_ptr用lambda,这也是C库中的不足shared_ptr设计成函数模板更好又同样的思路为什么unique_ptr还用类模板。2.4.3.3.2自己实现shared_ptr的删除器这里我们可以和库中一样讲构造函数弄成函数模板但是这里有一个问题要保持删除器(D)的类型如果不保持析构函数不知道怎么释放但是要保持这个类型而这个删除器的类型是构造函数的不是一整个类的用不了(除非和unique_ptr一样设置成类模板写成整个类的构造可以用析构也可以用就定义一个成员变量定制一个删除器但是我们也知道这种方法的不好所以我们不使用这种),这里我们可以使用包装器实现(各种类型都释放掉)此时还要一个问题对于正常的类型时就为调用删除器那个构造此时包装器未走列表初始化此时就使用包装器(function)的默认构造function默认构造是个空的可调用对象空的去调用就会抛异常所以此时还得给一个缺省值。有了删除器后任何资源都可以给智能指针管理(可以控制释放方式)。【有删除器的shared_ptr代码】template class T class Shared_ptr { public: Shared_ptr(T* ptr nullptr) :_ptr(ptr), _Pcount(new std::atomicint(1)) { } Shared_ptr(const Shared_ptrT sp) :_ptr(sp._ptr), _Pcount(sp._Pcount) { (*_Pcount); } template class D//定义有删除器的 Shared_ptr(const Shared_ptrT sp, D del) : _ptr(sp._ptr), _Pcount(sp._Pcount), _del(del) { (*_Pcount); } Shared_ptrT operator(const Shared_ptrT sp) { if (_ptr ! sp._ptr) // 防止直接或间接和自己赋值指向同一块空间 { release(); // 在赋值前将自身引用计数-- _ptr sp._ptr; _Pcount sp._Pcount; (*_Pcount); return *this; } } int use_count() const { return _Pcount-load(); } T* get() const { return _ptr; } // const 防止权限放大 T* operator-() { return _ptr; } T operator*() { return *_ptr; } ~Shared_ptr() { release(); } private: void release() { if (--(*_Pcount) 0) { _del(_ptr); delete _Pcount; } } private: T* _ptr; std::atomicint* _Pcount; // 不能设置为静态static int count std::functionvoid(T*) _del [](T* ptr) { delete ptr; };//这里给缺省值 //是赋值普通类型没有走删除器版本的构造所以finction的默认值 };2.4.4 weak_ptr2.4.4.1 weak_ptr由来和用途shared_ptr已经非常完美了但还是存在一些问题。下面是一个链表结构我们将两块资源分别交给智能指针shared_ptr进行管理然后在每一块资源中还存在一个shared_ptr智能指针指向对方资源。此时就会导致循环引用的问题而weak_ptr就是对shared_ptr进行补贴解决循环引用的问题【库中的weak_ptr】•weak_ptr不⽀持RAII也不⽀持访问资源所以我们看⽂档发现weak_ptr构造时不⽀持绑定到资 源只⽀持绑定到shared_ptr绑定到shared_ptr时不增加shared_ptr的引⽤计数•weak_ptr也没有重载operator*和operator-等因为他不参与资源管理那么如果他绑定的shared_ptr已经释放了资源那么他去访问资源就是很危险的。weak_ptr⽀持expired检查指向的资源是否过期use_count也可获取shared_ptr的引⽤计数weak_ptr想访问资源时可以调⽤lock返回⼀个管理资源的shared_ptr如果资源已经被释放返回的shared_ptr是⼀个空对象如果资源没有释放则通过返回的shared_ptr访问资源是安全的2.4.4.2shared_ptr的 循环引用问题2.4.4.2 .1循环引用问题怎么形成的【代码】struct ListNode { int _data; shared_ptrListNode _prev; shared_ptrListNode _next; ~ListNode() { cout ~ListNode() endl; // for debug } }; int main() { shared_ptrListNode n1 new ListNode; shared_ptrListNode n2 new ListNode; n1-_prev n2; n2-_next n1; return 0; }上面的场景1.右边的节点什么时候释放呢左边节点中的_next管着呢_next析构后右边的节点就释放了。2._next什么时候析构呢_next是左边节点的的成员左边节点释放_next就析构了。3.左边节点什么时候释放呢左边节点由右边节点中的_prev管着呢_prev析构后左边的节点就释放了。4._prev什么时候析构呢_prev是右边节点的成员右边节点释放_prev就析构了。•⾄此逻辑上成功形成回旋镖似的循环引⽤谁都不会释放就形成了循环引⽤导致内存泄漏2.4.4.2 2循环引用问题怎么解决这里ListNode结构体中的_next和_prev改成weak_ptr即可。【为什么可以】我们看一下weak_ptr的底层weak_ptr是一个特殊的智能指针只有在循环引用中才会使用。其底层无指针构造不支持资源管理(RAII),只绑定到对应的shared_ptr而引用计数不变。所以把shared_ptr赋给weak_ptr时两者可以指向同一块资源但是其引用计数不变。【解决代码】template class T class Shared_ptr { public: Shared_ptr(T * ptr nullptr) :_ptr(ptr), _Pcount(new std::atomicint(1)) {} Shared_ptr(const Shared_ptrT sp) :_ptr(sp._ptr), _Pcount(sp._Pcount) { (*_Pcount); } template class D Shared_ptr(const Shared_ptrT sp, D del) :_ptr(sp._ptr), _Pcount(sp._Pcount), _del(del) { (*_Pcount); } Shared_ptrT operator(const Shared_ptrT sp) { if (_ptr ! sp._ptr) // 防止直接或间接和自己赋值指向同一块空间 { release(); // 在赋值前将自身引用计数-- _ptr sp._ptr; _Pcount sp._Pcount; (*_Pcount); return *this; } } int use_count() const { return _Pcount-load(); } T* get() const { return _ptr; } // const 防止权限放大 T* operator-() { return _ptr; } T operator*() { return *_ptr; } ~Shared_ptr() { release(); } private: void release() { if (--(*_Pcount) 0) { _del(_ptr); delete _Pcount; } } private: T* _ptr; std::atomicint* _Pcount; // 不能设置为静态static int count std::functionvoid(T*) _del [](T* ptr) { delete ptr; }; }; // 不参与Shared_ptr的引用计数不支持RAII。 // 但如果Shared_ptr指向的空间释放可能会导致野指针。 // 所以实际Weak_ptr会有一个use_count观察Shared_ptr引用计数 template class T class Weak_ptr { public: Weak_ptr() :_ptr(nullptr) {} Weak_ptr(const Shared_ptrT sp) :_ptr(sp.get()) {} Weak_ptrT operator(const Shared_ptrT sp) { _ptr sp.get(); return *this; } T operator*() { return *_ptr; } T* operator-() { return _ptr; } private: T* _ptr; };三 C11和boost中智能指针的关系•在C98时C出现了第一个智能指针auto_ptr。•在99年时boost成立。并且在C98和C11之间boost给出了更为实用的智能指针scoped_ptr和shared_ptr和weak_ptr。•C TR1引入了shared_ptr等。不过注意的是TR1并不是标准版•C 11引入了unique_ptr和shared_ptr和weak_ptr。其中unique_ptr对应boost中的scoped_ptr并且这些智能指针的实现原理是参照boost中的相关智能指针实现的本篇文章就到此结束欢迎大家订阅我的专栏欢迎大家指正希望有所能帮到读者更好了解C相关知识 C相关知识就到此结束后面我将继续更新Linux相关知识。觉得有帮助的还请三联支持一下~后续会不断更新算法与数据结构相关知识我们下期再见。