C++ - 智能指针

1. 智能指针基本概念

1.1 RAll

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

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

  • 不需要显式地释放资源。
  • 采用这种方式,对象所需的资源在其生命期内始终保持有效。

在C++中,智能指针一共定义了4种: auto_ptr、unique_ptr、shared_ptr 和 weak_ptr。其中 auto_ptr 在 C++11已被摒弃,在C++17中已经移除不可用。

C++11 中提供了三种智能指针,使用这些智能指针时需要引用头文件 #include <memory>

 

1.2 智能指针概念

  在c++中,动态内存的管理式通过一对运算符来完成的:new,在动态内存中为对象分配空间并返回一个指向该对象的指针,我们可以选择对对象进行初始化;

delete,接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。动态内存的使用很容易出现问题,因为确保在正确的时间释放内存是极其困难的。有时使用完对象后,忘记释放内存,造成内存泄漏的问题。

  •   所谓的智能指针本质就是一个类模板,它可以创建任意的类型的指针对象,当智能指针对象使用完后,对象就会自动调用析构函数去释放该指针所指向的空间

下面是智能指针的基本框架,所有的智能指针类模板中都需要包含一个指针对象构造函数析构函数

 

2. 原始指针的问题

原始指针的问题大家都懂,就是如果忘记删除,或者删除的情况没有考虑清楚,容易造成悬挂指针(dangling pointer)或者说野指针(wild pointer)。

我们看个简单的例子

objtype *p = new objtype();
p -> func();
delete p;

上面的代码结构是我们经常看到的。里面的问题主要有以下两点:

1.代码的最后,忘记执行delete p的操作。

2.第一点其实还好,比较容易发现也比较容易解决。比较麻烦的是,如果func()中有异常,delete p语句执行不到,这就很难办。有的同学说可以在func中进行删除操作,理论上是可以这么做,实际操作起来,会非常麻烦也非常复杂。

此时,智能指针就可以方便我们控制指针对象的生命周期。在智能指针中,一个对象什么情况下被析构或被删除,是由指针本身决定的,并不需要用户进行手动管理,是不是瞬间觉得幸福感提升了一大截,有点幸福来得太突然的意思,终于不用我自己手动删除指针了。

 

3. 常见的智能指针

3.1 auto_ptr

auto_ptr是c++98版本库中提供的智能指针,该指针解决上诉的问题采取的措施是管理权转移的思想,也就是原对象拷贝给新对象的时候,原对象就会被设置为nullptr,此时就只有新对象指向一块资源空间。

如果auto_ptr调用拷贝构造函数或者赋值重载函数后如果再去使用原来的对象的话,那么整个程序就会崩溃掉(因为原来的对象被设置为nullptr),这对程序是有很大的伤害的.所以很多公司会禁用auto_ptr智能指针。

3.1.1 特点

  ①只能有一个智能指针占用对象的所有权。

3.1.2 案例一

3.1.3 案例二

注: auto_ptr不能赋值和用于初始化另一个对象。如果进行了此类操作,则原智能指针对象无效。

3.1.4 案例三

3.1.5 案例四

3.1.6 缺点

  ①auto_ptr不能赋值和用于初始化另一个对象,如果经行了此类操作,则原智能指针对象无效。——例如 案例二和案例三。

  ②auto_ptr只能管理单个对象,不能管理对象数组。——例如 案例四以表明。auto_ptr析构函数使用的是delete,而不是delete[ ]。

  ③在C++11标准中已经废弃auto_ptr。——原因:<1>它可能导致对同一块堆空间进行多次delete。<2>当两个智能指针都指向同一个堆空间时,每个智能指针都会delete一下这个堆空间, 这会导致未定义行为。

 

3.2 unique_ptr

unique_ptr是c++11版本库中提供的智能指针,它直接将拷贝构造函数和赋值重载函数给禁用掉,因此,不让其进行拷贝和赋值。

unique_ptr的拷贝函数和赋值重载函数

3.2.1 特点

  ①unique_ptr可以看成是auto_ptr的代替品,因为它对对象的所有权比较专一,所以叫unique

  ②不允许进行拷贝构造和赋值操作。

  ③允许函数返回unique_ptr类型指针

  ④支持对象数组。

 

3.2.2 案例一

3.2.3 案例二:对案例一进行正确修改


3.2.4 案例三

对应特点③——允许函数返回unique_ptr类型指针

 

3.2.5 unique_ptr的缺点

  无法进行拷贝构造和赋值操作。

 

3.3 sharshared_ptr

share_ptr是c++11版本库中的智能指针,shared_ptr允许多个智能指针可以指向同一块资源,并且能够保证共享的资源只会被释放一次,因此是程序不会崩溃掉。

3.3.1 特点

  ①shared_ptr定义智能指针A拷贝构造产生智能指针B的时候,A和B共享一个对象。

  ②shared_ptr定义智能指针A赋值给shared_ptr定义的智能指针B的时候,A和B共享一个对象。

3.3.2 实现原理

shared_ptr采用的是引用计数原理来实现多个shared_ptr对象之间共享资源:

  • shared_ptr在内部会维护着一份引用计数,用来记录该份资源被几个对象共享。
  • 当一个shared_ptr对象被销毁时(调用析构函数),析构函数内就会将该计数减1。
  • 如果引用计数减为0后,则表示自己是最后一个使用该资源的shared_ptr对象,必须释放资源。
  • 如果引用计数不是0,就说明自己还有其他对象在使用,则不能释放该资源,否则其他对象就成为野指针。

引用计数是用来记录资源对象中有多少个指针指向该资源对象。

销毁过程:

 

3.3.3 案例一 ——创建对象

3.3.4 案例二 —— 拷贝构造、赋值

3.3.5 案例三 —— 两个shared_ptr同时指向普通指针

缺点:循环引用的时候,会带来内存泄漏。

         p指针指向空间被释放了两次,这种问题被比喻成“二龙治水”,部分编译器对此做了优化不会报错。

shared_ptr智能指针详解图

 

3.3.6 案例四 —— 循环引用问题


 可以看到目前的引用计数都是2,当代码执行完之后,才会调用析构函数,可以看到这两个都没有调用析构函数,产生这种问题的原因是:A引用了B,B又引用了A。

解决这种问题需要使用弱引用计数 weak_ptr

改完之后再重新编译一下

可以看到引用计数都是1,当sp1和sp2脱离生命周期的时候就会执行析构函数。

 

3.4 weak_ptr

弱引用指针,不会影响对象的引用计数,通常与shared_ptr一起使用

3.4.1 特点

  ①weak_ptr是为了配合shared_ptr而引入的一种智能指针来协助shared_ptr工作。

  ②weak_ptr的含义为“弱引用”,它的构造和析构不会引起引用计数的增加或减少。

  ③它可以从一个shared_ptr或另一个weak_ptr产生。

  ④他没有重载*和 -> ,所以不能通过它访问对象内部的成员。

  ⑤可以使用它提供的lock()获得一个可用的shared_ptr对象。

 

3.4.2 案例一:weak_ptr智能指针创建


3.4.3 案例二

调用lock()创建shared_ptr指针时才会引用实际对象。

在lock()成功时会延长shared_ptr对象的生命周期,因为它递增了一个引用计数。

expired()函数用作判断weak_ptr智能指针是否过期,过期返回true。

 expired()用于判断object2是否过期,即object2中是否还有指向内容,如果没有过期就继续。

3.4.4 案例三 帮助shared_ptr解决循环引用问题


 

4. 智能指针总结

  1. std::unique_ptr:独占型指针,保证同一时间内只有一个智能指针可以指向对象。

  2. std::shared_ptr:共享型指针,当存在多个shared_ptr指向同一个对象时,该对象不会自动销毁。

  3. std::weak_ptr:弱引用指针,不会影响对象的引用计数,通常与shared_ptr一起使用。

  4. std::auto_ptr(已废弃):自动释放型指针,在C++11后被std::unique_ptr取代。

 

 

更多参考文档:

————————————————


————————————————
版权声明:本文为CSDN博主「L2018026029」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/L2018026029/article/details/126571301

posted @ 2023-10-13 10:07  [BORUTO]  阅读(22)  评论(0编辑  收藏  举报