• C++智能指针


    诞生背景

    在实际的C++开发过程中,我们经常会遇到诸如程序运行中突然崩溃、程序运行所用的内存越来越多最终不得不重启等问题,这些问题往往都是内存管理资源不当造成的。比如:

    1. 有些内存资源已经释放,但指向它的指针并没有改变指向,最终成为了野指针,并且后续还在使用;
    2. 有些内存资源已经被释放,后期又试图再释放一次,最终导致重复释放同一块内存会使程序运行崩溃;
    3. 没有及时释放不再使用的内存资源,造成内存泄漏,程序占用的内存资源越来越多。

    针对以上情况,C++提供了更友好的内存管理机制,让程序员更专注于开发项目的各个功能上,而不是自己进行内存管理。事实上,早在1959年前后,就有人提出“垃圾自动回收”机制,“垃圾”指的是那些不再使用或者没有任何指针指向的内存空间,而“回收”指的是将这些“垃圾”收集起来以便再次利用。

    智能指针介绍

    在C++11标准中,增加了nique_ptr、shared_ptr 以及 weak_ptr 这 3 个智能指针来实现堆内存的自动回收。

    智能指针和普通指针用法相似,智能指针的本质是一个模板类,对普通指针进行了封装,通过在构造函数中初始化分配内存,在析构函数中释放内存,达到自己管理内存,不需要手动管理内存的效果,因此智能指针可以在适当时机自动释放分配的内存,即智能指针可以很好地避免“忘记释放内存而导致内存泄漏”问题

    接下来对shared_ptr的原理和用法做详细的解释说明。

    智能指针用法以及原理

    通过构造函数、std::make_shared和reset初始化三种初始化方式

    构造函数初始化方式

    //1.通过如下两种方式,可以构造出 shared_ptr 类型的空智能指针,对于空的指针,其初始引用计数方式为0,不是1
    std::shared_ptr<int> p1;             //不传入任何实参
    std::shared_ptr<int> p2(nullptr);    //传入空指针 nullptr
    
    
    //2.创建指针时,可以明确指向
    std::shared_ptr<int> p3(new int(10)); //指向一个存有10这个int类型数据的堆内存空间
    
    //3.调用拷贝构造函数
    std::shared_ptr<int> p4(p3);//或者 std::shared_ptr p4 = p3; 
    //如果P3为空,则P4也为空,其引用计数初始值为0,反之,则表明P4和P3指向同一块堆内存,同时堆内存的引用次数会加1.
    
    //4.调用移动构造函数
    std::shared_ptr<int> p5(std::move(p4)); //或者 std::shared_ptr p5 = std::move(p4);
    // 即P5拥有了P4的堆内存,而P4则变成了空智能指针
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    std::make_shared 初始化方式

    std::make_shared 初始化方式,C++11 标准中提供了 std::make_shared 模板函数,其可以用于初始化 shared_ptr 智能指针

    //1.定义一个空的智能指针
    std::shared_ptr<int> p6 = std::make_shared<int>(); 
    
    //2.创建指针,并明确指向
    std::shared_ptr<int> p7 = std::make_shared<int>(10);
    
    //3.auto关键字代替std::shared_ptr,p8指向一个动态分配的空vector
    auto p8 = make_shared<vector<int>>();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    reset初始化

    //创建了一个指针,并明确指向
    //调用reset(new xxx())重新赋值时,智能指针首先是生成新对象,然后将旧对象的引用计数减1(当然,如果发现引用计数为0时,则析构旧对象),然后将新对象的指针交给智能指针保管。
    std::shared_ptr<int> p8 = nullptr;
    p8.reset(new int(1));
    
    //当智能指针中有值的时候,调用reset()会使引用计数减1,如果引用计数为0时,则析构旧对象
    p8.reset();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    获取原始指针

    智能指针一般都提供了get()成员函数,用来执行显示转换,即返回智能指针内部的原始指针。

     std::shared_ptr<int> p9(new int(5));
     int *pInt = p9.get();
    
    • 1
    • 2

    如果需要调用成员函数,由于几乎所有的智能指针都重载了 * ,->操作符,所以直接使用把智能指针当做一般的指针变量来使用就可以了。但是有时候需要传递参数,如果参数是 T*,那么传递一个智能指针类是无法识别的,因此需要使用原始指针。

    使用智能指针时需要注意的问题

    ①不要用一个原始指针初始化多个shared_ptr,原因在于,会造成二次销毁,如下所示:

     int *p5 = new int;
     std::shared_ptr<int> p6(p5);
     std::shared_ptr<int> p7(p5);// logic error
    
    • 1
    • 2
    • 3

    ②不要在函数实参中创建shared_ptr。因为C++的函数参数的计算顺序在不同的编译器下是不同的。正确的做法是先创建好,然后再传入。

    function(shared_ptr<int>(new int)); // 先创建shared_ptr(new int),避免不同编译器的不同行为
    
    • 1

    ③shared_ptr不支持动态数组,但是本地编译器由通过了?

    C++17及以后是支持动态数组的,C++11/14是不支持的,只要是最新的编译器是没问题的。

    ④避免循环引用。智能指针最大的一个陷阱是循环引用,循环引用会导致内存泄漏。

    ⑤线程安全问题。参考智能指针线程安全问题

  • 相关阅读:
    k8s之pod流量分析
    HTTP和HTTPS的区别、 HTTPS运行原理
    QGraphicsView、QGraphicsScene、QGraphicsItem的应用
    【图形学】30 前向渲染多光照场景代码理解
    P2331 [SCOI2005]最大子矩阵(dp)
    数字化时代,企业转型发展可能会有哪些特征?
    第 299 场周赛 第四题 6103. 从树中删除边的最小分数
    面试复习题——底层
    压缩感知的概述梳理(4)
    video 按钮全屏
  • 原文地址:https://blog.csdn.net/qq_30326609/article/details/128021228