C++11 智能指针
创始人
2024-03-24 22:05:53

文章目录

    • 1. 智能指针出现的意义
      • 1.1 内存泄漏
      • 1.2 智能指针初识
    • 2. C++标准库中的智能指针
      • 2.1 auto_ptr
      • 2.2 std::unique_ptr
      • 2.3 std::shared_ptr
      • 2.4 std::weak_ptr
    • 3. 智能指针中的定制删除

前言: 智能指针,它是指针嘛?它是一个类具有指针的功能,我去,那不是还有一个迭代器嘛,迭代器不就是一个类具有指针的功能。注意这俩可不敢混淆。迭代器是自定义对象的指针,可以这么理解,迭代器的出现使得自定义对象,也可以像内置类型一般进行指针操作。那么智能指针的出现,又有什么意义呢?


1. 智能指针出现的意义

1.1 内存泄漏

内存泄漏可以分为两类:

  1. 堆空间上的内存泄漏:
    堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用。
  2. 系统资源中的内存泄漏 :
    指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

智能指针可以解决内存泄漏的问题,它相对于一种预防手段。因为C++没有回收机制嘛,所以内存泄漏的问题,解决起来十分困难。

比如:你虽然严格按照 new delete ,malloc free 这样写代码。但是 如果程序中途抛异常 有可能就会跳过 你写的 delete 或是 free ,这很难受。造成了内存泄漏。

怎么说呢,内存泄漏是很危险的,尤其是那种长时间运行的程序。一旦出现内存泄漏,会导致程序越来越卡,甚至导致服务器 宕机。所以 程序员再处理内存,指针之类的 都格外小心。有没有一种机制,可以帮助我们 减轻些负担呢?那就是智能指针

1.2 智能指针初识

智能指针利用的是,RALL技术:利用对象的生命周期来控制程序资源。

简单来说:构造类对象,会自动调用构造函数;对象 销毁时,会自动调用对象的析构函数。利用类对象这一特性,就不需要我们手动的释放内存空间。

对于这个大家基本上都懂,但是我也用代码演示一下:

#include
using namespace std;class A
{
private:int _a;
public:A(int a = 0):_a(a){cout << "构造:A()" << endl;}~A(){cout << "析构:~A()" << endl;}
};int main()
{A a;return 0;
}

构造一个A类对象a,我们来看程序运行结果:

在这里插入图片描述

嗯,那么我们来实现一个简易版本的智能指针:

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

智能指针原理总结:

  1. RAII特性
  2. 重载operator*和opertaor->,具有像指针一样的行为。

我们来简单的使用一下,上面的智能指针:

    int* iptr = new int(2);SmartPtr sptr(iptr);*sptr = 10;

来看程序的运行:

在这里插入图片描述

可以看到,是自动释放new 出来的 空间的。


2. C++标准库中的智能指针

C++11之前是有一个:

  • auto_ptr

但由于被喷的惨,所以基本没人用。

C++11 更新后,新给出了三类智能指针:

  1. std::unique_ptr
  2. std::shared_ptr
  3. std::weak_ptr

下面我会一 一 介绍,

2.1 auto_ptr

我们上面不是写过一个简易版本的智能指针,大家可以再看一下。会发现我没有写拷贝构造和赋值重载。其实智能指针,难点就是这俩。我先用上面的简易智能指针去完成一下拷贝,看看会出现什么问题。

	SmartPtr sptr(new int(1));SmartPtrsptr1(sptr);

在这里插入图片描述
抛异常了,我们试着,捕获一下:

   try {SmartPtr sptr(new int(1));SmartPtrsptr1(sptr);}catch (const exception& e){cout << e.what() << endl;}

这样不能捕获,因为这个异常抛的是我们自定义类型的,所以不好搞。

我直接说原因吧,sptr 赋值给 sptr1 默认的拷贝构造是 浅拷贝,所以导致同一个块资源被释放了 两次。

怎么解决这个问题呢?有多种方式,就这个问题衍生出的多类的智能指针。

auto_ptr是这样解决的:管理权转移的思想,也就是说,这一块资源我交给要拷贝我的人来管理,我自己呢撒手掌柜,不管了。

这是感性的理解,还是代码实现一下:

    templateclass auto_ptr{private:T* _ptr;public:auto_ptr(T* ptr = nullptr):_ptr(ptr){}~auto_ptr(){if (_ptr){delete _ptr;}}auto_ptr(auto_ptr& tmp):_ptr(tmp._ptr){tmp._ptr = nullptr;}auto_ptr& operator=(auto_ptr &tmp){if (_ptr){delete _ptr;}_ptr = tmp._ptr;tmp._ptr = nullptr;return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}};

关键是这一步:

在这里插入图片描述

给段代码,通过调试帮助大家理解:

    ly::auto_ptr aptr(new int(1));ly::auto_ptr aptr1(aptr);ly::auto_ptr aptr2(new int(2));aptr2 = aptr1;

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

但是有一个明显的缺陷,那就是拷贝构造,完成了权限转移,直接把我原来管理的资源置为空。虽然我不再管理了,直接把置空了,那么我很难受。我虽然没有权力去释放这块资源,但是 我连访问都成问题了。这样做是不是有点太绝了。

比如:

    ly::auto_ptr aptr(new int(1));ly::auto_ptr aptr1(aptr);cout << *aptr << endl;

现在就变成了堆空指针的解引用,必然抛异常。所以auto_ptr 这样的做法有点太 一刀两断了。我被别人拷贝了,把管理权交出去了,没有权利去释放资源,但是不能直接把我置空,导致我 无法访问资源。

综上:auto_ptr 用的少,但是 前人踩坑,后人才能避坑。


2.2 std::unique_ptr

怎么说呢,unique_ptr更加暴力,直接就是不允许发生智能指针的拷贝和赋值。呵呵,很强势,当然很简单,我直接实现一下:

   templateclass unique_ptr{private:T* _ptr;public:unique_ptr(T* ptr = nullptr):_ptr(ptr){}~unique_ptr(){if (_ptr){delete _ptr;}}unique_ptr(unique_ptr& tmp) = delete;unique_ptr& operator=(unique_ptr& tmp) = delete;T& operator*(){return *_ptr;}T* operator->(){return _ptr;}};
}

就是将 拷贝构造和赋值重载 delete 关键字修饰一下。

这个好理解对吧。不过多赘述了哈。要提一点就是:

在这里插入图片描述
库里面有第二个模板参数,这个模板参数是指定删除。放后面讲。


2.3 std::shared_ptr

这才是 真正意义上支持拷贝和赋值额 智能指针。利用的是引用计数的思想,也就是说 类中有用于保存 指向此块资源对象的个数,等指向此资源的对象只剩下一个时,如果要析构才会释放资源,其余情况 都是 指向资源个数 减一。

图解:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

简易实现:

我们先来实现一个简易版本的,不考虑线程安全问题,那么想让 所有对象 共用一份 count 计数,有几种方式呢?

我给出两种:

  • 将引用计数设置为静态成员变量
  • 使用指针,引用计数 是堆上 开辟的,所有对象都可以通过指针,来访问同一块堆上空间

那么 简易点就是 第一种方式嘛:

templateclass shared_ptr{public:static int _count;T* _ptr;public:shared_ptr(T* ptr =nullptr):_ptr(ptr){_count = 1;}~shared_ptr(){if (--_count == 0 && _ptr){delete _ptr;}}shared_ptr(shared_ptr& tmp){// 防止自己赋值给自己if (_ptr != tmp._ptr){_ptr = tmp._ptr;_count = tmp._count;_count++;}}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}};templateint shared_ptr::_count = 0;

这就是支持拷贝构造,不考虑线程安全的版本,很简单哈。


其实线程安全问题的解决无非就是 加个锁。 多个线程 对 count进行 ++ - - 操作,这里是线程不安全的,所以 对count 操作的地方,都需要加锁。

templateclass shared_ptr{private:T* _ptr;int* _count;mutex* _mutex;public:shared_ptr(T* ptr=nullptr):_ptr(ptr),_count(new int(1)),_mutex(new mutex) {}~shared_ptr(){release();}shared_ptr(shared_ptr& tmp){if (_ptr != tmp._ptr){_ptr = tmp._ptr;_count = tmp._count;_mutex = tmp._mutex;tmp.addcount();}}shared_ptr& operator= (shared_ptr& tmp){if (_ptr != tmp._ptr){release();_ptr = tmp._ptr;_count = tmp._count;_mutex = tmp._mutex;tmp.addcount();}return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get(){return _ptr;}int get_count(){return *_count;}void release(){_mutex->lock();bool flag = false;if (--(*_count) == 0&&_ptr){delete _ptr;delete _count;flag = true;}_mutex->unlock();if (flag == true){delete _mutex;}}void addcount(){_mutex->lock();(*_count)++;_mutex->unlock();}};

测试代码:

        shared_ptr sptr(new int(1));shared_ptr sptr1(sptr);shared_ptr sptr2(new int(2));sptr2 = sptr1;

shared_ptr 其实还有一个问题需要格外注意循环引用。循环引用会导致,本该释放的资源,得不到释放,也就是说 count 加多了,这解释有点牵强,大家一会看图理解:

首先给出一个例子,链表的节点:

在这里插入图片描述
节点里面的指针,我们可以用智能指针嘛?试着用一下,因为 智能指针 还是有好处的,它可以预防 内存泄漏 对吧,但是这里会出现 循环引用的问题:

struct  Node
{int val;ly::shared_ptr _next;ly::shared_ptr _prev;~Node(){cout << "~Node" << endl;}
};

假如我这样使用节点:

    shared_ptr n1(new Node);shared_ptr n2(new Node);cout<

看结果,是对的:

在这里插入图片描述

假如我让它俩互相指向呢?

    shared_ptr n1(new Node);shared_ptr n2(new Node);cout<_next = n2;n2->_prev = n1;cout << n1.get_count() << endl;cout << n2.get_count() << endl;

在这里插入图片描述

发现尽然没有析构,没释放资源。程序都退出了,这就是内存泄漏。

造成这个的原因,我们来分析一下:

刚开始 没问题:

在这里插入图片描述
但是由于n1->_next = n2; n2->_prev = n1; 所以 count ++了:

在这里插入图片描述
如果进行析构,那么 就是 count – ,它减完之后,变为1 ,所以 不会进行 资源释放。

其实问题已经分析出来了,count 如果不 ++ 那么 资源还能够释放,因为互相指向,所以它俩的count 都 ++了。那么有没有解决办法呢?那就是 std::weak_ptr ,它呢,就是 不参与 资源管理,虽然指向了某块资源,但是 count 不会 ++。


2.4 std::weak_ptr

weak_ptr 其实就是 专门用于 解决 shared_ptr 中循环指向的问题的。

废话不多说,直接就是 将节点中的 shared_ptr 换成 weak_ptr 就可以了:

在这呢 先使用 标准库中的 shared_ptr 和 weak_ptr ,之后再模拟实现weak_ptr,

struct  Node
{int val;std::weak_ptr _next;std::weak_ptr _prev;~Node(){cout << "~Node" << endl;}
};int main()
{std::shared_ptr n1(new Node);std::shared_ptr n2(new Node);cout<_next = n2;n2->_prev = n1;cout << n1.use_count() << endl;cout << n2.use_count() << endl;return 0;
}

看运行结果,很明显完成了资源释放:

在这里插入图片描述
而且 发现 count的值 没有变成 2,原因很简单 weak_ptr 智能指针不参与 资源管理。

那么 我们来模拟实现一下,weak_ptr ,很简单:

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

对吧,就是不让它参与资源管理就行,唯一需要注意的就是它的拷贝构造,可以是shared_ptr 也可以是 weak_ptr 。

在这里插入图片描述


3. 智能指针中的定制删除

像上面所有的智能指针模拟实现都是 new 和delete ;但是 还有别的情况 比如 delete [] , free ,fclose() 。对吧,所以呢 智能指针 提供了 定制删除,默认情况下是 delete 。

在这里插入图片描述

看 默认参数D 是 default_delete< T >:

在这里插入图片描述
看到了吧,这是啥?仿函数呀,昂,可以 。我们一会 来模拟实现一下 free 版本的

但是 shared_ptr 中 定制删除 不是给的模板参数,而是 在构造函数重载中的一个:

在这里插入图片描述

所以定制删除在智能指针中的使用,要自己去标准库中查看,但定制删除一般都是是仿函数。区别就是 模板参数 给的是类型,构造函数中 给的是 对象。

但是 定制删除 如果是在 sharde_ptr中给的不就是个对象嘛,所以也可以给 lambda表达式。


所以 我们先来 给出 两个定制删除器:

  1. delete []
template
struct DeleteArray
{void operator()(const T* ptr){delete[] ptr;}
};
  1. fclose()
struct DeleteFile
{void operator()(FILE* ptr){fclose(ptr);}
};

使用起来也很简单:

std::unique_ptr> up2(new A[10]);
std::unique_ptr up3(fopen("test.txt", "w"));
std::shared_ptr sp2(new A[10], DeleteArray());
std::shared_ptr sp3(fopen("test.txt", "w"), DeleteFile());

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...