ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

引用计数与强弱指针

2022-02-02 16:02:28  阅读:114  来源: 互联网

标签:std cnt cout 强弱 计数 引用 ptr 指针


引用计数是一种很实用的设计,我们可以在很多地方看到它的使用:

  1. 比如GUI中的自动垃圾回收机制,就是这么搞的,比如我们在一个按键的回调中需要创建一个view并显示,那么我们会new一个pageiew的对象,比如var view_instance = new xx::view ,然后present这个view到窗口上显示. 当关闭这个窗口的时候,我们可以令view_instance = null.这样就不再有人引用这个new出来的xx::view, 然后很快,这块内存就会被垃圾回收机制回收掉
  2. 比如linux中的设备驱动中对硬件资源的管理。举个例子,hal对用户提供的接口有open, close, read, write等。在驱动中,不同的open操作对应的是同一个硬件资源,只有第一次才会真正的分配资源,后面每个使用者open一次只不过是引用计数+1, 每个使用者close则对应引用计数-1,只有引用计数减到0的时候才真正的释放对应的硬件资源
  3. C++中或者android中的智能指针,强弱指针等

在我看来当一个库提供的接口中含有reference, 我认为它的语义就是,不需要用户来管理内存的分配和释放,只需要用户在合适的时候reference和dereference即可。C++中更进了一步,搞出来了智能指针的概念,把这块工作进一步的自动化了。那么引用计数法的缺点是什么呢?有两个缺点:一个是增加了开销,需要为每个对象维护一个计数。另一个才是真正致命的问题,无法处理循环引用的问题,也就是下面这个例子

#include <iostream>
#include <memory>

class A;
class B;

class A {
public:
    std::shared_ptr<B> pointer;
    ~A() {
        std::cout << "A 被销毁" << std::endl;
    }
};

class B {
public:
    std::shared_ptr<A> pointer;
    ~B() {
        std::cout << "B 被销毁" << std::endl;
    }
};

int main() {
    std::shared_ptr<A> a = std::make_shared<A>();
    std::shared_ptr<B> b = std::make_shared<B>();
    a->pointer = b;
    b->pointer = a;
    return 0;

}

运行结果是 A, B 都不会被销毁,这是因为 a,b 内部的 pointer 同时又引用了 a,b,这使得 a,b 的引用计数均变为了 2,而离开作用域时,a,b 智能指针被析构,但是只是把内部的引用计数减一,这样就导致了 a,b 对象指向的内存区域引用计数不为零,而外部已经没有办法找到这块区域了,也就造成了内存泄露

为了解决这个问题,C++中引入了弱指针 std::weak_ptr,std::weak_ptr是一种弱引用(相比较而言 std::shared_ptr 就是一种强引用)

#include <iostream>
#include <memory>

class A;
class B;

class A {
public:
    std::weak_ptr<B> pointer;
    ~A() {
        std::cout << "A 被销毁" << std::endl;
    }
};

class B {
public:
    std::shared_ptr<A> pointer;
    ~B() {
        std::cout << "B 被销毁" << std::endl;
    }
};

int main() {
    std::shared_ptr<A> a(new A); //A的引用计数是1
    std::shared_ptr<B> b(new B); //B的引用计数是1
    a->pointer = b; //B的引用计数是1
    b->pointer = a; //A的引用计数是2
    return 0;

}

为了更好的理解为什弱引用可以解决这个问题,我们逐行分析一下源码,看看到底干了什么。首先先看下shared ptr的实现:

template <class T>
class WeakPtr; //为了用weak_ptr的lock(),来生成share_ptr用,需要拷贝构造用

template <class T>
class SharePtr
{
public:
    SharePtr(T *p = 0) : _ptr(p)  //通过普通指针构造一个智能指针
    {
        cnt = new Counter(); //构造的时候创建一个Counter,用于计数
        if (p)
            cnt->s = 1; //初始化为1
        cout << "in construct " << cnt->s << endl;
    }
    ~SharePtr()
    {
        release();
    }

    SharePtr(SharePtr<T> const &s) //拷贝构造
    {
        cout << "in copy con" << endl;
        _ptr = s._ptr;  //指向同一个对象
        (s.cnt)->s++; //引用计数+1
        cout << "copy construct" << (s.cnt)->s << endl;
        cnt = s.cnt; //把引用计数同步到当前shared ptr
    }
    SharePtr(WeakPtr<T> const &w) //为了用weak_ptr的lock(),来生成share_ptr用,需要拷贝构造用
    {
        cout << "in w copy con " << endl;
        _ptr = w._ptr;
        (w.cnt)->s++;
        cout << "copy w  construct" << (w.cnt)->s << endl;
        cnt = w.cnt;
    }
    SharePtr<T> &operator=(SharePtr<T> &s)
    {
        if (this != &s) 
        {
            release(); //解除或者-1当前shared ptr的引用
            (s.cnt)->s++;
            cout << "assign construct " << (s.cnt)->s << endl;
            cnt = s.cnt; 
            _ptr = s._ptr; //重定向到新的ptr
        }
        return *this;
    }
    T &operator*()
    {
        return *_ptr;
    }
    T *operator->()
    {
        return _ptr;
    }
    friend class WeakPtr<T>; //方便weak_ptr与share_ptr设置引用计数和赋值

protected:
    void release()
    {
        cnt->s--;
        cout << "release " << cnt->s << endl;
        if (cnt->s < 1)
        {
            delete _ptr; //无强引用,释放这块内存;即使有弱引用也要释放
            if (cnt->w < 1) //有弱引用,保留cnt指向的这块内存
            {
                delete cnt;
                cnt = NULL;
            }
        }
    }

private:
    T *_ptr;
    Counter *cnt;
};

然后再看一下弱引用的源码:

template <class T>
class WeakPtr
{
public: //给出默认构造和拷贝构造,其中拷贝构造不能有从原始指针进行构造
    WeakPtr()
    {
        _ptr = 0;
        cnt = 0;
    }
    WeakPtr(SharePtr<T> &s) : _ptr(s._ptr), cnt(s.cnt)//从shared ptr进行构造
    {
        cout << "w con s" << endl;
        cnt->w++; //对s的对象的弱引用计数+1
    }
    WeakPtr(WeakPtr<T> &w) : _ptr(w._ptr), cnt(w.cnt)
    {
        cnt->w++;
    }
    ~WeakPtr()
    {
        release();
    }
    WeakPtr<T> &operator=(WeakPtr<T> &w)
    {
        if (this != &w)
        {
            release();
            cnt = w.cnt;
            cnt->w++;
            _ptr = w._ptr;
        }
        return *this;
    }
    WeakPtr<T> &operator=(SharePtr<T> &s)
    {
        cout << "w = s" << endl;
        release();
        cnt = s.cnt;
        cnt->w++;
        _ptr = s._ptr;
        return *this;
    }
    SharePtr<T> lock()
    {
        return SharePtr<T>(*this);
    }
    bool expired()
    {
        if (cnt) //结合上面的shared ptr看,如果对象被析构,这个有可能是null
        {
            if (cnt->s > 0)//若还有强引用,则说明没有expired
            {
                cout << "empty" << cnt->s << endl;
                return false;
            }
        }
        return true; //是null的话,铁定内存已经被释放了
    }
    friend class SharePtr<T>; //方便weak_ptr与share_ptr设置引用计数和赋值
    
protected:
    void release()
    {
        if (cnt)
        {
            cnt->w--;
            cout << "weakptr release" << cnt->w << endl;
            if (cnt->w < 1 && cnt->s < 1)
            {
                cnt = NULL; //可以看到弱引用计数不会影响对象的释放
            }
        }
    }

private:
    T *_ptr;
    Counter *cnt;
};

注意,delete一个对象的时候,除了会执行这个对象的析构函数,还会自动执行这个对象内部的其它对象的析构,举个例子:

class B {
public:
    std::weak_ptr<A> pointer;
    ~B() {
        std::cout << "B 被销毁" << std::endl;
    }
};

class C {
public:
    ~C() { std::cout << "c的析构" << std::endl; }
    B mB;
};

int main() {
    C *c = new C;
    delete c;
    return 0;
}

执行结果为:

 

 

那么为什么引入弱引用可以解决循环引用的问题也就迎刃而解了。a被析构的时候,执行release(), 对A的引用计数从2减到1(b还持有一个),然后b析构的时候,执行release, B的引用计数是1减到0(a对B是弱引用),这样就会delete B, B的析构函数会执行,然后B中的对象会被执行析构,也就是一个指向A的shared ptr对象会被析构,于是这个时候A的引用计数由1减到0,A也最终被释放了。

 


 

后记:

  • 虽然通过弱引用指针可以有效的解除循环引用,但这种方式必须在程序员能预见会出现循环引用的情况下才能使用,也可以是说弱引用仅仅是一种编译期的解决方案,如果程序在运行过程中出现了循环引用,还是会造成内存泄漏的。因此,不要认为只要使用了智能指针便能杜绝内存泄漏
  • 弱引用:它仅仅是对象存在时候的引用,当对象不存在时弱引用能够检测到,从而避免非法访问,弱引用也不会修改对象的引用计数。这意味这弱引用它并不对对象的内存进行管理,在功能上类似于普通指针,然而一个比较大的区别是,弱引用能检测到所管理的对象是否已经被释放,从而避免访问非法内存
  • 智能指针不是线程安全的,多线程使用时记得加锁

 

标签:std,cnt,cout,强弱,计数,引用,ptr,指针
来源: https://www.cnblogs.com/Arnold-Zhang/p/15860413.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有