zoukankan      html  css  js  c++  java
  • 深入学习c++--智能指针(二) weak_ptr(打破shared_ptr循环引用)

    1. 几种智能指针

    1. auto_ptr: c++11中推荐不使用他(放弃)

    2. shared_ptr: 拥有共享对象所有权语义的智能指针 

    3. unique_ptr: 拥有独有对象所有权语义的智能指针 

    4. weaked_ptr: 到 std::shared_ptr 所管理对象的弱引用 

    1.1 weak_ptr

    参考:https://zh.cppreference.com/w/cpp/memory/weak_ptr

    • std::weak_ptr 是一种智能指针,它对被 std::shared_ptr 管理的对象存在 非拥有性(“弱”)引用。在访问所引用的对象前必须 先转换为 std::shared_ptr

    • std::weak_ptr 用来 表达临时所有权的概念

      • 当某个对象只有存在时才需要被访问,而且随时可能被他人删除时,可以使用 std::weak_ptr 来跟踪该对象。

      • 需要获得临时所有权时,则将其转换为 std::shared_ptr,此时如果原来的 std::shared_ptr被销毁,则该对象的生命期将被延长至这个临时的 std::shared_ptr 同样被销毁为止。

    • std::weak_ptr 的另一用法是:打断 std::shared_ptr 所管理的对象组成的环状引用。(打破shared_ptr的循环引用)

      • 若这种环被孤立(例如无指向环中的外部共享指针),则 shared_ptr 引用计数无法抵达零,而内存被泄露。

      • 能令环中的指针之一为弱指针以避免此情况。

    循环引用的问题:该被调用的析构函数没有被调用

    #include <iostream>
    #include <memory>
    using namespace std;
     
    class Parent;
    typedef std::shared_ptr<Parent> ParentPtr;
    
    class Child
    {
    public:
        ParentPtr father;
        Child() {
            cout << "hello Child" << endl;
        }
        ~Child() {
            cout << "bye Child
    ";
        }
    };
    
    typedef std::shared_ptr<Child> ChildPtr;
    
    class Parent {
    public:
        ChildPtr son;
        Parent() {
            cout << "hello parent
    ";
        }
        ~Parent() {
            cout << "bye Parent
    ";
        }
    };
    
    void testParentAndChild()
    {
        ParentPtr p(new Parent());
        ChildPtr c(new Child());
        p->son = c;
        c->father = p; 
    }
    
    int main()
    {
        testParentAndChild();
        return 0;
    }

    问题:c只有调用p的析构的时候,才能被释放。p只有调用c的析构的时候,才能被释放。。形成了循环引用,造成了内存泄露

    因此,引入新的智能指针,weak_ptr

    1.2 weak_ptr基本用法

    一旦外部指向shared_ptr资源失效,那么weak_ptr管理的资源自动失效 

    #include <iostream>
    #include <cassert>
    #include <memory>
    using namespace std;
     
    class Object
    {
    public:
        Object(int id) : m_id(id) {
            std::cout << "init obj " << m_id << std::endl;
        }    
        ~Object() {
            std::cout << "bye bye " << m_id << std::endl;
        }
        int id() const {
            return m_id;
        }
    private:
        int m_id;
    };
    
    
    void sharedPtrWithWeakPtr()
    {
        typedef std::shared_ptr<Object> ObjectPtr;
        typedef weak_ptr<Object> WeakObjectPtr;
        
        ObjectPtr obj(new Object(1));
        
        // 一旦外部指向shared_ptr资源失效,那么weak_ptr管理的资源自动失效 
        
        WeakObjectPtr weakObj2;           // 裸指针 
        WeakObjectPtr weakObj(obj);       // 指向shared_ptr指针 
        WeakObjectPtr weakObj3(obj);   
        cout << "obj use count is " << obj.use_count() << endl;   // 1--尽管weak_ptr也指向obj,但他只是监听者,本身并不影响引用计数次数 
        {
            // weak_ptr使用方法 
            // 外部至少还有一个shared_ptr来管理资源,同时p自己本身的资源 -- p.use_count >= 2 
            auto p = weakObj.lock();         // auto == ObjectPtr
            if (p) {
                cout << p.unique() << endl;  // 0 -- p.use_count() >= 2
            }
            else {
            }
        }
        // 不指向某份资源 
    //    obj.reset();
    //    {
    //        auto p = weakObj.lock();      // auto == ObjectPtr
    //        if (p) {
    //            assert(false);
    //        } 
    //        else {
    //            cout << "null" << endl;   // 如果调用reset,就会执行这句话 
    //        }
    //    } 
        
        // 此时, Object(1)已经失效 
        obj.reset(new Object(2)) ;
        {
            auto p = weakObj.lock();       // 返回值如果有效, 在外部必须有weakObj指向同一个资源 
            if (p) {
                assert(false);
            }
            else {
                cout << "null " << endl;   // null
            }
        }
        
        weakObj = obj;    // 又有效了 
        // 想知道weak_ptr有没有管理一份资源(外部有没有资源), 又不想生成一个shared_ptr
        if (weakObj.expired()) {
            // 资源过期了 -- true 
            cout << "no ptr" << endl;
        }
        else {
            cout << "have resource
    ";
        }
    }
    
    int main()
    {
        
        sharedPtrWithWeakPtr();
        return 0;
    
    }

    1.3 解决类之间循环引用

    一旦外部指向shared_ptr资源失效,那么weak_ptr管理的资源自动失效 

    #include <iostream>
    #include <cassert>
    #include <memory>
    using namespace std;
     
    class Parent;
    typedef std::shared_ptr<Parent> ParentPtr;
    typedef std::weak_ptr<Parent> WeakParentPtr;
    
    class Child
    {
    public:
        WeakParentPtr father;                 // 只有一环换成 weak_ptr, 即可打破环 
        Child() {
            cout << "hello Child" << endl;
        }
        ~Child() {
            cout << "bye Child
    ";
        }
    };
     
    typedef std::shared_ptr<Child> ChildPtr;
    typedef std::weak_ptr<Child> WeakChildPtr;
    
    class Parent {
    public:
        ChildPtr son;                 
        Parent() {
            cout << "hello parent
    ";
        }
        ~Parent() {
            cout << "bye Parent
    ";
        }
    };
    
    
    void testParentAndChild()
    {
        ParentPtr p(new Parent());
        ChildPtr c(new Child());
        p->son = c;            
        c->father = p;        
        cout << (c->father).use_count() << endl;
        cout << (p->son).use_count() << endl;
    }
    
    int main()
    {
        testParentAndChild();
        return 0;
    }

    析构函数成功调用, 注意析构顺序,谁是weak_ptr对象,就先析构谁。

    1.4 用 enable_shared_from_this从this转换到shared_ptr

    • 类中函数接口需要一个本对象智能指针的const引用 (如何生成本对象的智能指针)

    • 用 enable_shared_from_this从this转换到shared_ptr  (使用CRTP来使用 本对象的智能指针)

    主要代码:

    handleChildAndParent(shared_from_this(), ps);

    完整代码:

    #include <iostream>
    #include <cassert>
    #include <memory>
    using namespace std;
     
    class Parent;
    typedef std::shared_ptr<Parent> ParentPtr;
    typedef std::weak_ptr<Parent> WeakParentPtr;
     
    class Child : public std::enable_shared_from_this<Child>  // 奇异模板参数 CRTP
    {
    public:
        WeakParentPtr father;                 // 只有一环换成 weak_ptr, 即可打破环 
        Child() {
            cout << "hello Child" << endl;
        }
        ~Child() {
            cout << "bye Child
    ";
        }
        void CheckRelation() {
            
        }
    };
     
    typedef std::shared_ptr<Child> ChildPtr;
    typedef std::weak_ptr<Child> WeakChildPtr;
    
    void handleChildAndParent(const ParentPtr& p, const ChildPtr& c);
    
    class Parent : public enable_shared_from_this<Parent> {
    public:
        WeakChildPtr son;                 
        Parent() {
            cout << "hello parent
    ";
        }
        ~Parent() {
            cout << "bye Parent
    ";
        }
        void CheckRelation() {
            auto ps = son.lock();
            if (ps) {
                //原理: 类中存在weak_ptr指针,通过一系列方式转换成 shared_ptr,然后传参 
                handleChildAndParent(shared_from_this(), ps);    // 使用CRTP来使用 本对象的指针 ★ ★ ★ ★ 
            }
            cout << "after call checkRelation
    ";
        }
    };
    
    void testParentAndChild()
    {
        ParentPtr p(new Parent());
        ChildPtr c(new Child());
        p->son = c;            
        c->father = p;        
        cout << (c->father).use_count() << endl;
        cout << (p->son).use_count() << endl;
        
        p->CheckRelation();
    }
    
    void handleChildAndParentRef(const Parent& p, const Child& c)
    {
        auto cp = c.father.lock();
        auto pc = p.son.lock();
        if (cp.get() == &p && pc.get() == &c) 
        {
            cout << "right relation" << endl;
        }
        else {
            cout << "oop !!!
    ";
        }
    }
    
    // const xxx&: 减少拷贝次数 
    void handleChildAndParent(const ParentPtr& p, const ChildPtr& c)
    {
        auto cp = c->father.lock();
        auto pc = p->son.lock();
        if (cp == p && pc == c)
        {
            cout << "right relation
    ";
        }
        else {
            cout << "oop!!!
    ";
        }
    }
    
    int main()
    {
        testParentAndChild();
        return 0;
    }

     

  • 相关阅读:
    C#多线程编程实战1.5检测线程状态
    C#多线程编程实战1.4终止线程
    C#多线程编程实战1.3等待线程
    C#多线程编程实战1.2暂停线程(休眠)
    C#多线程编程实战1.1创建线程
    C#中base的作用
    C#继承
    C#中return的两个作用
    Windows下完全卸载node.js并安装node.js的多版本管理工具nvm-windows
    执行gulp build报错
  • 原文地址:https://www.cnblogs.com/douzujun/p/10803365.html
Copyright © 2011-2022 走看看