C++-第二十章:智能指针
目录
第一节:std::auto_ptr
第二节:std::unique_ptr
4-1.循环引用
4-2.删除器
下期预告:
智能指针的作用是防止指针出作用域时忘记释放内存而造成内存泄漏,智能指针重载了[]、*、->来模拟原生指针的行为,智能指针包含在<memory>中。
智能指针有几种,各有其特点。
第一节:std::auto_ptr
std::auto_ptr 的特点是没有拷贝构造和拷贝赋值,只有移动构造和移动赋值。所以用一个智能指针给另一个智能指针传参时,实际上是把管理权转移了:
int main() { std::auto_ptr<int> ptr1(new int(1)); std::auto_ptr<int> ptr2 = ptr1; std::cout << ptr1.get() << std::endl; // 打印原生指针的内容 std::cout << ptr2.get() << std::endl; // 打印原生指针的内容 return 0; }
这样 ptr1 就指向nullptr,它是不安全的,很容易出现野指针的解引用。
但是它的好处是不容易出现两个std::auto_ptr管理同一块空间,出作用域时两次释放同一空间。
第二节:std::unique_ptr
unique意为单一的,它没有拷贝构造、赋值构造、移动构造、移动赋值。相比于std::auto_ptr更加安全。
第三节:std::shared_ptr
shared意为分享,它运行进行拷贝构造、赋值构造、移动构造、移动赋值。
为了解决多次释放内存的问题,它引入了一个计数器,这个计数器是静态变量,被指向同一空间的std::shared_ptr 共享。只要创建一个智能指针时,计数器就会加1,智能指针析构时会判断计数器是否为1,如果不是,说明自己还不是最后一个管理该内存的指针,就不会释放内存,如果是,就会释放内存。
std::shared_ptr 除了直接使用原生指针赋值,还可以使用std::make_shared进行赋值:
std::shared_ptr<int> ptr(std::make_shared<int>(1));
使用它的优势是让内存碎片更小,因为new来构造智能指针时,要分配两次内存:(1)智能指针对象(2)指针指向的内存空间。
std::make_shared,会一次性申请足够的内存用来存放智能指针对象和指针指向的内存空间,内存只分配了一次,所以内存碎片更小。
第四节:std::shared_ptr的缺陷
4-1.循环引用
std::shared_ptr 的计数器的存在,使得最后一个智能指针才释放内存,但是当最后两个智能指针的析构互为对方析构的前提时,就会导致内存泄漏:
![]()
#include <memory> class Node { public: std::shared_ptr<Node> _prev = nullptr; std::shared_ptr<Node> _next = nullptr; }; int main() { auto n1 = std::make_shared<Node>(); auto n2 = std::make_shared<Node>(); n1->_next = n2; // n1 持有 n2 的 shared_ptr n2->_prev = n1; // n2 持有 n1 的 shared_ptr return 0; }
如上图所示,智能指针p1、p2分别指向两个相互指向的节点,此时它们的引用计数都是2。
p1释放内存的前提是p2的_prev先析构,
p2的_prev析构的前提是p2先释放内存,
p2释放内存前提是p1的_next先析构,
p1的_next析构的前提是p1释放内存,
这样就构成闭环了,谁也释放不了。
解决方法是将_next和_prev的类型都改成std::weak_ptr,它是一种资源的弱引用,提供一种可观察而非拥有的访问方式。
它的特点是不直接管理资源的释放,引用计数也不会增加,将_next、_prev改成std::weak_ptr类型即可打破循环引用。
4-2.删除器
智能指针默认的析构方式是对指针进行delete操作,这对文件指针无能为力,因为文件指针释放内存的方式是fclose,此时可以定制一个删除器传入智能指针:
std::shared_ptr<FILE> ptr(fopen("test.txt","w"),[](FILE* _ptr){fclose(_ptr);});
ptr 在析构时就会调用传入的可调用对象关闭文件指针了。
下期预告:
第二十一章是C++的最后一章,将介绍类的特殊设计。