• C++11学习之share_ptr和weak_ptr


    一、shared_ptr学习

    1.shared_ptr和weak_ptr 基础概念

    • shared_ptr与weak_ptr智能指针均是C++ RAII的一种应用,可用于动态资源管理
    • shared_ptr基于“引用计数”模型实现,多个shared_ptr可指向同一个动态对象,并维护了一个共享的引用计数器,记录了引用同一对象的shared_ptr实例的数量。当最后一个指向动态对象的shared_ptr销毁时,会自动销毁其所指对象(通过delete操作符)。
    • shared_ptr的默认能力是管理动态内存,但支持自定义的Deleter以实现个性化的资源释放动作。
    • weak_ptr用于解决“引用计数”模型循环依赖问题,weak_ptr指向一个对象,并不增减该对象的引用计数器

    2.shared_ptr的基本操作

    #include <memory>
    #include <iostream>
    
    struct Foo {
        Foo() { std::cout << "Foo...
    "; }
        ~Foo() { std::cout << "~Foo...
    "; }
    };
    
    struct D { 
        //删除p所指向的Foo对象
        void operator()(Foo* p) const {
            std::cout << "Call delete for Foo object...
    ";
            delete p;
        }
    };
    
    int main()
    {   
        // constructor with no managed object
        std::shared_ptr<Foo> sh1;
    
        // constructor with object
        std::shared_ptr<Foo> sh2(new Foo);
        std::shared_ptr<Foo> sh3(sh2);
        std::cout << sh2.use_count() << '
    ';
        std::cout << sh3.use_count() << '
    ';
    
        //constructor with object and deleter
        std::shared_ptr<Foo> sh4(new Foo, D());
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    构造方法: 
    1.通过make_shared函数构造 
    auto s_s = make_shared(“hello”);

    2.通过原生指针构造 
    int* pNode = new int(5); 
    shared_ptr s_int(pNode); 
    //获取原生指针 
    int* pOrg = s_int.get();

    3.通过赋值函数构造shared_ptr

    4.重载的operator->, operator *,以及其他辅助操作如unique()、use_count(), get()等成员方法。

    3 实验智能指针引用计数,增加和减少的规律

    实验的主要内容有: 
    1.shared_ptr变量在生命周期中销毁后,引用计数是否减1? 
    2.shared_ptr作为函数参数,分为传值和传引用,引用计数如何变化? 
    2.函数返回值为shared_ptr类型时,引用计数是否会变化?

    带着这几个问题,我们来看下代码.

    #include <iostream>
    #include <memory>
    using namespace std;
    
    void Func1(shared_ptr<int> a)
    {
        cout<<"Enter Func1"<<endl;
        cout<<"Ref count: "<<a.use_count()<<endl;
        cout<<"Leave Func1"<<endl;
    }
    
    shared_ptr<int> Func2(shared_ptr<int>& a)
    {
        cout<<"Enter Func2"<<endl;
        cout<<"Ref count: "<<a.use_count()<<endl;
        cout<<"Leave Func2"<<endl;
        return a;
    }
    
    int main()
    {
        //构造一个指向int类型对象的指针aObj1,引用计数+1
        shared_ptr<int> aObj1(new int(10));
        cout<<"Ref count: "<<aObj1.use_count()<<endl;
    
        {
            //同aObj1,不过由于生存周期在括号内,所以aObj2会被销毁
            shared_ptr<int> aObj2 = aObj1;
            cout<<"Ref count: "<<aObj2.use_count()<<endl;//引用计数-1
        }
    
        //在调用函数时,参数为shared_ptr类型,参数为传值类型,智能指针引用计数+1
        Func1(aObj1);
    
        //在调用函数时,参数为shared_ptr类型,参数为传引用类型,智能指针引用计数不变
        Func2(aObj1);
    
        shared_ptr<int> aObj3 = Func2(aObj1);//引用计数+1
        cout<<"Ref count:"<<aObj3.use_count()<<endl;
    
        return 0;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    运行结果如下: 
    这里写图片描述

    有效的掌握好智能指针的引用计数的变化规律,才能把程序写的更好.

    4. shared_ptr的应用场景以及使用注意事项

    4.1 对象之间“共享数据”,对象创建与销毁“分离” 
    4.2 放入容器中的动态对象,使用shared_ptr包装,比unique_ptr更合适 
    4.3 管理“动态数组”时,需要制定Deleter以使用delete[]操作符销毁内存,因为shared_ptr并没有针对数组的特化版本(unique_ptr有针对数组的特化版本)

    5.shared_ptr的线程安全问题

    1. 同一个shared_ptr被多个线程读,是线程安全的;
    2. 同一个shared_ptr被多个线程写,不是 线程安全的;
    3. 共享引用计数的不同的shared_ptr被多个线程写,是线程安全的。 
      对于第三点,我们一般采用: 
      对于线程中传入的外部shared_ptr对象,在线程内部进行一次新的构造,例如: sharedptr AObjTmp = outerSharedptrObj;

    二、weak_ptr学习

    我们先搞清楚,weak_ptr为什么出现,或者说它是为了解决什么问题而存在的(存在即合理),哈哈

    class Parent
    {
    public:
        shared_ptr<Child> child;
    };
    
    class Child
    {
    public:
        shared_ptr<Parent> parent;
    };
    
    shared_ptr<Parent> pA(new Parent);
    shared_ptr<Child> pB(new Child);
    pA->child = pB;
    pB->parent = pA;
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在Parent类中存储了指向Child类对象的智能指针成员变量,而在Child类中也存储了指向Parent类对象的智能指针成员变量,如此就会造成环形引用,这个成因在C++中很好解释.

    要解决环形引用的问题,没有特别好的办法,一般都是在可能出现环形引用的地方使用weak_ptr来代替shared_ptr。说到了weak_ptr,那下面就接着总结weak_ptr吧。

    下面我们来一起学习下weak_ptr这个东东

    weak_ptr指向shared_ptr指针指向的对象的内存,却并不拥有该内存。 
    但是,使用weak_ptr成员lock,则可返回其指向内存的一个shared_ptr对象,且在所指对象内存已经无效时,返回指针空值(nullptr)。由于weak_ptr是指向shared_ptr所指向的内存的,所以,weak_ptr并不能独立存在。

    #include <iostream>
    #include <memory>
    using namespace std;
    
    void Check(weak_ptr<int> &wp)
    {
        shared_ptr<int> sp = wp.lock(); // 重新获得shared_ptr对象
        if (sp != nullptr)
        {
            cout << "The value is " << *sp << endl;
        }
        else
        {
            cout << "Pointer is invalid." << endl;
        }
    }
    
    int main()
    {
        shared_ptr<int> sp1(new int(10));
        shared_ptr<int> sp2 = sp1;
        weak_ptr<int> wp = sp1; // 指向sp1所指向的内存
    
        cout << *sp1 << endl;
        cout << *sp2 << endl;
        Check(wp);
    
        sp1.reset();
        cout << *sp2 << endl;
        Check(wp);
    
        sp2.reset();
        Check(wp);
    
        system("pause");
        return 0;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    学习编程最好的方式就是一步步的跟踪去调试. 
    借鉴上面的代码,我们在使用weak_ptr时也要当心,时刻需要判断weak_ptr对应的shared_ptr是否为空,weak_ptr并不会增加shared_ptr的引用计数.

    另附一篇地址,讲解为何不同的shared_ptr对象可以被多线程同时修改(即使这些shared_ptr对象管理着同一个对象的指针)

    https://blog.csdn.net/jiangfuqiang/article/details/8292906

  • 相关阅读:
    springboot 集成RabbitMQ
    服务接口API限流 Rate Limit 续
    服务接口API限流 Rate Limit
    聊下并发和Tomcat线程数
    java 线程池 异步任务
    Tomcat中更改网站根目录和默认页的配置方法
    QPS从0到4000请求每秒,谈达达后台架构演化之路
    分布式与集群是什么 ? 区别是什么?
    大型网站技术架构演变总结
    提升高并发量服务器性能解决思路
  • 原文地址:https://www.cnblogs.com/wangshaowei/p/9374389.html
Copyright © 2020-2023  润新知