C++ 智能指针简介
文章目录1.由来2.基本思想3.引用计数4.实现模板参考文献1.由来C 动态内存管理是通过一对运算符来完成的new 用于申请内存空间调用对象构造函数初始化对象并返回指向该对象的指针。delete 接收一个动态对象的指针调用对象的析构函数销毁对象释放与之关联的内存空间。动态内存的管理在实际操作中并非易事因为确保在正确的时间释放内存是极其困难的有时往往会忘记释放内存而产生内存泄露有时在上游指针引用内存的情况下释放了内存就会产生非法的野指针悬挂指针。为了更容易且更安全的管理动态内存C 推出了智能指针smart pointer类型来管理动态对象。智能指针存储指向动态对象的指针用于动态对象生存周期的控制能够确保自动正确的销毁动态分配的对象防止内存泄露。对动态内存的管理可以引申为对系统资源的管理但是 C 程序中动态内存只是最常使用的一种资源其他常见的资源还包括文件描述符file descriptor、互斥锁mutex locks、图形界面中的字型和笔刷、数据库连接、以及网络sockets等这些资源事实上都可以使用智能指针来管理。2.基本思想智能指针的基本思想是以栈对象管理资源。考察如下示例voidremodel(std::stringstr){std::string*psnewstd::string(str);...if(weird_thing()){throwexception();}str*ps;deleteps;return;}如果在函数 remodel 中出现异常语句delete ps没有被执行那么将会导致 ps 指向的 string 的堆对象残留在内存中导致内存泄露。如何避免这种问题有人会说这还不简单直接在throw exception();之前加上delete ps;不就行了。是的你本应如此问题是很多人都会忘记在适当的地方加上delete语句连上述代码中最后的那句delete语句也会有很多人忘记吧如果你要对一个庞大的工程进行review往往会发现内存泄露时有发生对于程序而言这无疑是一场灾难这时我们会想当 remodel 这样的函数终止不管是正常终止还是由于出现了异常而终止函数体内的局部变量都将自动从栈内存中删除因此指针 ps 占据的内存将被释放如果 ps 指向的内存也被自动释放那该有多好啊。我们知道析构函数有这个功能。如果 ps 有一个析构函数该析构函数将在 ps 过期时自动释放它指向的内存。但 ps 的问题在于它只是一个常规指针不是有析构凼数的类对象指针。如果 ps 是一个局部的类对象它指向堆对象则可以在 ps 生命周期结束时让它的析构函数释放它指向的堆对象。通俗来讲 智能指针就是模拟指针动作的类。所有的智能指针都会重载 - 和 * 操作符。智能指针的主要作用就是用栈智能指针离开作用域自动销毁时调用析构函数来释放资源。当然智能指针还不止这些还包括复制时可以修改源对象等。智能指针根据需求不同设计也不同写时复制赋值即释放对象拥有权限、引用计数、控制权转移等。3.引用计数什么是引用计数智能指针有时需要将其管理的对象的所有权转移给其它的智能指针使得多个智能指针管理同一个对象比如C STL中的shared_ptr支持多个智能指针管理同一个对象。这个时候智能指针就需要知道其引用的对象总共有多少个智能指针在引用在它也就是说智能指针所管理的对象总共有多少个所有者我们称之为引用计数Reference Counting因为智能指针在准备释放所引用的对象时如果有其他的智能指针同时在引用这个对象时则不能释放而只能将引用计数减一。引用计数的目的引用计数是资源管理的一种技巧和手段智能指针使用了引用计数STL中的string也同样使用了引用计数并配合“写时复制”来实现存储空间的优化。总的来说使用引用计数有如下两个目的1节省内存提高程序运行效率。如何很多对象拥有相同的数据实体存储多个数据实体会造成内存空间浪费所以最好做法是让多个对象共享同一个数据实体。2记录引用对象的所有者数量在引用计数为0时让对象的最后一个拥有者释放对象。其实智能指针的引用计数类似于Java的垃圾回收机制Java 的垃圾判定很简单如果一个对象没有引用所指那么该对象为垃圾系统就可以回收了。智能指针实现引用计数的策略。大多数 C 类用三种方法之一来管理指针成员1不管指针成员。复制时只复制指针不复制指针指向的对象实体。当其中一个指针把其指向的对象的空间释放后其它指针都成了悬挂指针。这是一种极端做法。2当复制的时候即复制指针也复制指针指向的对象。这样可能造成空间的浪费。因为指针指向的对象的复制不一定是必要的。3 第三种就是一种折中的方式。利用一个辅助类来管理指针的复制。原来的类中有一个指针指向辅助类对象辅助类的数据成员是一个计数器和一个指针指向原来的对象。可见第三种方法是优先选择的方法智能指针实现引用计数的策略主要有两种辅助类与句柄类。使用句柄类尚未研究本文以辅助类为例来研究实现智能指针的引用计数。利用辅助类来封装引用计数和指向对象的指针。如此做智能指针辅助类对象与被引用对象的关系如下图所示辅助类将引用计数与智能指针类指向的对象封装在一起引用计数记录有多少个智能指针指向同一对象。每次创建智能指针时初始化智能指针并将引用计数置为1当智能指针q赋值给另一个智能指针r时即rq拷贝构造函数拷贝智能指针并增加q指向的对象的引用计数递减r原来指向的对象的引用计数。也就是说对一个智能指针进行赋值时赋值操作符减少左操作数所指对象的引用计数如果引用计数为减至0则删除对象并增加右操作数所指对象的引用计数。4.实现模板智能指针管理对象本质上是以栈对象来管理堆对象在《Effective C》的条款13中称之为资源获取即初始化RAIIResource Acquisition Is Initialization也就是说我们在获得一笔资源后尽量以独立的一条语句将资源拿来初始化某个资源管理对象。有时候获得的资源被拿来赋值而非初始化某个资源管理对象但不论哪一种做法获得一笔资源后应该立即放进资源管理对象中。智能指针就是一种资源管理对象提供的功能主要有如下几种1以指针的行为方式访问所管理的对象需要重载指针-操作符2解引用Dereferencing获取所管理的对象需要重载解引用*操作符3智能指针在其声明周期结束时自动销毁其管理的对象4引用计数、写时复制、赋值即释放对象拥有权限、控制权限转移。第4条是可选功能拥有四条中不同的功能对应着不同类型的智能指针比如C11在STL中引入的shared_ptr就实现了引用计数的功能已经被C11摒弃的auto_ptr[ 4 ] ^{[4]}[4]实现了赋值即释放对象拥有权限C11引入的unique_ptr则实现了控制权限的转移功能。根据智能指针的功能通常用类模板实现如下templatetypenameTclassSmartPointer{private:T*_ptr;public:// 构造函数SmartPointer(T*p):_ptr(p){}// 重载*操作符Toperator*(){return*_ptr;}// 重载-操作符T*operator-(){return_ptr;}// 析构函数~SmartPointer(){delete_ptr;}};参考文献Stanley B.Lippman著,王刚杨巨峰译.C Primer第五版.2013:400-422Scott Meyers著侯捷译.Effective C中文版第三版.2011:61-77C智能指针简单剖析shared_ptr基于引用计数智能指针实现