C++11【智能指针详解】
创始人
2024-01-20 22:34:47
0

智能指针

    • 🏞️1. 为什么引入智能指针?
    • 🌁2. 智能指针的使用及原理
      • 📖2.1 RAII思想
      • 📖2.2 智能指针的原理
    • 🌠3. 常见智能指针
      • 📖3.1 auto_ptr
      • 📖3.2 unique_ptr
      • 📖3.3 shared_ptr
      • 📖3.4 shared_ptr的循环引用问题
      • 📖3.5 weak_ptr
    • 🌌4. 定制删除器
    • ⛺5. C++11和boost智能指针的关系

🏞️1. 为什么引入智能指针?

我们来看这样一段代码:

#include 
using namespace std;int div()
{int a, b;cin >> a >> b;if (b == 0){throw "除0错误";}return a / b;
}void func()
{int* p1 = new int;int* p2 = new int;cout << div() << endl;delete p1;delete p2;
}int main()
{try{func();}catch (exception& e){cout << e.what() << endl;}return 0;
}

在这段代码中,如果在div函数中发生了除0错误,我们在main函数捕获异常,那么最终异常抛出后会跳转到main函数中的catch处,对于p1p2申请的资源就没有得到释放,就造成了内存泄露问题.

关于内存泄露介绍,在另一篇文章中有详细的介绍:

🌁2. 智能指针的使用及原理

📖2.1 RAII思想

RAII一种利用对象生命周期来控制程序资源(例如内存、文件句柄、网络连接、互斥量等)的简单技术.

获取到资源以后去初始化一个对象,将资源交给对象管理:资源获取即初始化

在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源. 借此,我们实际是把管理一份资源的责任托管给了一个对象,这种做法有两大好处

  1. 不需要显式的释放资源
  2. 采用这种方式,对象所需的资源在其生命周期内始终有效
//实现一个最简易的智能指针
template
class smart_ptr
{
public:smart_ptr(T* ptr = nullptr): _ptr(ptr){}//对象析构时自动释放所管理的资源~smart_ptr(){cout << "delete " << _ptr << endl;if (_ptr){delete _ptr;_ptr = nullptr;}}
private:T* _ptr;
};

此时,我们使用智能指针来代替裸指针,并让它发生除0错误:

#include 
#include "smart_ptr.h"
using namespace std;int div()
{int a, b;cin >> a >> b;if (b == 0){throw invalid_argument("除0错误");}return a / b;
}void func()
{smart_ptr p1(new int);smart_ptr p2(new int);cout << div() << endl;
}int main()
{try{func();}catch (exception& e){cout << e.what() << endl;}return 0;
}

image-20221108172706448

可以看到,刚才由于抛异常未能释放的资源现在可以正常释放.

📖2.2 智能指针的原理

我们所写的这个简单的智能指针smart_ptr还不能称其为智能指针,因为它还不具有指针的行为指针可以解引用,可以通过->去访问所指向空间内的内容,所以为了让它向指针一样,我们还需重载*->运算符.

template
class smart_ptr
{
public:smart_ptr(T* ptr = nullptr): _ptr(ptr){}~smart_ptr(){cout << "delete " << _ptr << endl;if (_ptr){delete _ptr;_ptr = nullptr;}}T& operator*() const{return *_ptr;}T* get() const{return _ptr;}T* operator->() const  //T*  = const T* _ptr{return _ptr;}
private:T* _ptr;
};

但是这样的智能指针是有问题的,试一下它的拷贝?

image-20221112103233500

那么,怎么去解决这个问题呢?

所以,接下来,我们来介绍几种C++标准库里的智能指针,来探究如何解决此问题

🌠3. 常见智能指针

📖3.1 auto_ptr

C++98版本的库中就提供了auto_ptr智能指针,它解决了拷贝赋值的问题,但也还有一些不足

auto_ptr实现原理:管理权转移的思想,下面将简化的模拟实现auto_ptr,主要体现它的思想:

namespace myPtr
{templateclass auto_ptr{public:auto_ptr(T* ptr = nullptr): _ptr(ptr){}~auto_ptr(){if (_ptr){cout << "delete " << _ptr << endl;delete _ptr;_ptr = nullptr;}}auto_ptr(auto_ptr& sp): _ptr(sp._ptr){sp._ptr = nullptr;}auto_ptr& operator=(auto_ptr& ap){if (this != &ap){//释放当前对象管理的资源if (_ptr)delete _ptr;//管理权转移_ptr = ap._ptr;ap._ptr = nullptr;}return *this;}T& operator*() const{return *_ptr;}T* operator->() const{return _ptr;}private:T* _ptr;};
}

从它的拷贝构造和赋值重载可以看出,它以资源管理权转移的方式解决拷贝的问题.

但是,这样也带来另一个问题:

int main()
{myPtr::auto_ptr p1(new int);myPtr::auto_ptr p2 = p1;//*p1 = 10;  p1已经没有对资源的管理权,不能再使用!return 0;
}

由于p1的资源管理权已经转移给了p2,那它自己就失去对资源的管理及使用权,造成p1悬空.

📖3.2 unique_ptr

unique_ptr是C++11才开始提供的一种智能指针,它相比于auto_ptr更靠谱.

unique的实现原理:简单粗暴的禁止拷贝

我们依然是模拟实现一个unique_ptr来理解它的原理:

template
class unique_ptr
{public:unique_ptr(T* ptr = nullptr): _ptr(ptr){}~unique_ptr(){if (_ptr){cout << "delete " << _ptr << endl;delete _ptr;_ptr = nullptr;}}unique_ptr(const unique_ptr&) = delete;unique_ptr& operator=(const unique_ptr&) = delete;T& operator*() const{return *_ptr;}T* operator->() const{return _ptr;}private:T* _ptr;
};

unique_ptr采用一种简单粗暴的方式来解决拷贝的问题:直接删除拷贝构造函数和赋值重载函数,禁止拷贝

📖3.3 shared_ptr

shared_ptr也是C++11开始提供的,它能够解决智能指针拷贝的问题,并且它不像unique_ptr那样直接禁止拷贝,它是支持拷贝的.

shared_ptr的原理:通过引用计数的方式来实现多个shared_ptr对象之间共享资源

  1. shared_ptr内部,给它所管理的资源维护了一份引用计数,用来记录该资源被几个对象共同管理(共享)
  2. 在对象被销毁时(调用析构函数),就说明自己不使用该资源了,对用的引用计数减1
  3. 如果引用计数减到0,就说明当前自己已经是最后一个使用该资源的对象,所以此时必须释放该资源
  4. 如果不是0.那就说明还有其他对象管理这份资源,此时只需将引用计数减减即可,不需要释放资源
template
class shared_ptr
{public:shared_ptr(T* ptr = nullptr): _ptr(ptr){//将引用计数初始化为1}~shared_ptr(){if (_ptr){cout << "delete " << _ptr << endl;delete _ptr;_ptr = nullptr;}}T& operator*() const{return *_ptr;}T* operator->() const{return _ptr;}private:T* _ptr;//维护一个引用计数
};

那么,这个引用计数,我们应该怎样去维护呢?

  1. 使用普通变量int _pCount

    显然不行,由于私有成员变量是两个对象独有的,假如我们有两个智能指针p1p2,对p1的引用计数的–不会影响p2

  2. 定义一个静态成员变量static int _pCount

    看似,好像可以,但是试一下如下场景:

    template
    class shared_ptr
    {public:shared_ptr(T* ptr = nullptr): _ptr(ptr){//在构造函数中将引用计数初始化为1_pCount = 1;}~shared_ptr(){if (--_pCount == 0 && _ptr){cout << "delete " << _ptr << endl;delete _ptr;_ptr = nullptr;}}shared_ptr(const shared_ptr& sp){_ptr = sp._ptr;++_pCount;}T& operator*() const{return *_ptr;}T* operator->() const{return _ptr;}private:T* _ptr;//维护一个引用计数static int _pCount;
    };template
    int shared_ptr::_pCount = 0;
    
    int main()
    {//_pCount = 1myPtr::shared_ptr p1(new int);//_pCount = 2;myPtr::shared_ptr p2(p1);//这一步又重新将_pCount置为1,导致最终只释放的一次资源myPtr::shared_ptr p3(new int);return 0;
    }
    
  3. 定义一个指针成员变量int* _pCount

    template
    class shared_ptr
    {public:shared_ptr(T* ptr = nullptr): _ptr(ptr), _pCount(new int(1)){}void Release(){if (--(*_pCount) == 0 && _ptr){cout << "delete " << _ptr << endl;delete _ptr;_ptr = nullptr;delete _pCount;_pCount = nullptr;}}~shared_ptr(){Release();}shared_ptr(const shared_ptr& sp){_ptr = sp._ptr;_pCount = sp._pCount;++(*_pCount);}shared_ptr& operator=(const shared_ptr& sp){//防止自赋值if (_ptr != sp._ptr){Release();_ptr = sp._ptr;_pCount = sp._pCount;++(*_pCount);}return *this;}T& operator*() const{return *_ptr;}T* operator->() const{return _ptr;}private:T* _ptr;//维护一个引用计数int* _pCount;
    };
    

    这就是我们最终模拟实现出的shared_ptr.

📖3.4 shared_ptr的循环引用问题

shared_ptr的循环引用问题

struct ListNode
{ListNode(const int& val = int()): _next(nullptr), _prev(nullptr), _val(val){}~ListNode(){cout << "~ListNode()" << endl;}myPtr::shared_ptr _next;myPtr::shared_ptr _prev;int _val;
};int main()
{myPtr::shared_ptr p1(new ListNode(1));myPtr::shared_ptr p2(new ListNode(2));p1->_next = p2;p2->_prev = p1;return 0;
}

在上面的代码中,我们应该是有两份ListNode节点需要释放,运行程序:

image-20221112164835524

没有任何节点被释放.

image-20221112165831225

这便是shared_ptr的循环引用问题.

解决方案:在引用计数的场景下,把节点中的_prev_next改成weak_ptr就可以

📖3.5 weak_ptr

weak_ptr原理:

node1->_next = node2;
node2->_prev = node1;//weak_ptr的_next和_prev不会增加node1和node2的引用计数

weak_ptr模拟实现

template
class weak_ptr
{public:weak_ptr(){}weak_ptr(const shared_ptr& sp){_ptr = sp._ptr;}weak_ptr& operator=(const shared_ptr& sp){if (_ptr != sp.get()){_ptr = sp.get();}return *this;}T* operator->(){return _ptr;}T& operator*(){return *_ptr;}private:T* _ptr;
};

🌌4. 定制删除器

在我们写的智能指针的析构函数中,统一都使用delete来释放资源,但是,如果资源不是用new申请出来的呢?比如:new T[],malloc,所以我们就需要定制删除器来规范释放资源的方式.

我们使用shared_ptr来做演示:

template
struct default_delete
{void operator()(T* ptr){delete ptr;}
};template>
class shared_ptr
{public:shared_ptr(T* ptr = nullptr): _ptr(ptr), _pCount(new int(1)){}void Release(){if (--(*_pCount) == 0 && _ptr){cout << "delete " << _ptr << endl;Del del;del(_ptr);_ptr = nullptr;delete _pCount;_pCount = nullptr;}}~shared_ptr(){Release();}shared_ptr(const shared_ptr& sp){_ptr = sp._ptr;_pCount = sp._pCount;++(*_pCount);}shared_ptr& operator=(const shared_ptr& sp){//防止自赋值if (_ptr != sp._ptr){Release();_ptr = sp._ptr;_pCount = sp._pCount;++(*_pCount);}return *this;}T& operator*() const{return *_ptr;}T* operator->() const{return _ptr;}T* get() const{return _ptr;}private:T* _ptr;//维护一个引用计数int* _pCount;
};
//定制new T[]类型的删除器
template
struct DeleteArray
{void operator()(T* ptr){delete[] ptr;}
};int main()
{myPtr::shared_ptr> p(new int[10]);return 0;
}

⛺5. C++11和boost智能指针的关系

  1. C++98中产生了第一个智能指针auto_ptr
  2. C++ boost库给出了更实用的scoped_ptr和shared_ptr以及weak_ptr
  3. C++ TR1,引入了shared_ptr等,不过需要注意的是TR1并不是标准版
  4. C++ 11,引入了unique_ptr和shared_ptr以及weak_ptr,需要注意的是unique_ptr对应的boost的scoped_ptr,并且这些智能指针的实现原理是参考了boost库中的.

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
一帆风顺二龙腾飞三阳开泰祝福语... 本篇文章极速百科给大家谈谈一帆风顺二龙腾飞三阳开泰祝福语,以及一帆风顺二龙腾飞三阳开泰祝福语结婚对应...
美团联名卡审核成功待激活(美团... 今天百科达人给各位分享美团联名卡审核成功待激活的知识,其中也会对美团联名卡审核未通过进行解释,如果能...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...