• 聊聊 C++ 中的几种智能指针 (下)


    一:背景

    上一篇我们聊到了C++ 的 auto_ptr ,有朋友说已经在 C++ 17 中被弃用了,感谢朋友提醒,今天我们来聊一下 C++ 11 中引入的几个智能指针。

    1. unique_ptr

    2. shared_ptr

    3. weak_ptr

    看看它们都怎么玩。

    二:三大智能指针详解

    1. unique_ptr

    上一篇在说 auto_ptr 的时候聊过一个术语叫 控制权转移,言外之意就是下面的代码会 访问违例 。

    
    void test() {
    	int* i = new int(10);
    
    	auto_ptr<int> ptr1(i);
    	auto_ptr<int> ptr2 = ptr1;  //将 ptr1 的控制台送给了 ptr2
    
    	cout << *ptr1 << endl;
    }
    
    int main() {
    	test();
    }
    
    

    这段代码用了赋值运算符,可能是程序员的无心之举,却引来程序的崩溃,那能不能规避下这种 无心 的行为呢? 当然是可以的,屏蔽掉 auto_ptr 中的赋值运算符不就可以了哈,C++ 中的 unique_ptr 就是这么实现的,可以看下源码。

    
    class unique_ptr {
    
    public:
    	unique_ptr(const unique_ptr&) = delete;
    	unique_ptr& operator=(const unique_ptr&) = delete;
    };
    
    

    可以看到,unique_ptr 将 =,复制构造函数 都给屏蔽了,所以再硬写的话,必然是报错。

    2. shared_ptr

    这个算是纯正的 引用计数,高级语言中的 引用跟踪 经常会拿它做比较,说它存在循环引用等各种问题。

    在循环引用之前,先说下它的简单用法。

    
    void test() {
    	int* i = new int(10);
    	shared_ptr<int> ptr1(i);
    	shared_ptr<int> ptr2 = ptr1;
    	shared_ptr<int> ptr3 = ptr2;
    
    	printf("ptr.refcnt= %d, ptr2.refcnt=%d, ptr3.refcnt=%d \n", 
    												   ptr1.use_count(), 
    												   ptr2.use_count(), 
    												   ptr3.use_count());
    }
    
    int main() {
    	test();
    }
    
    

    再看下源码,大概在内部共同维护了对 _Ref_count_base 引用。

    
    class _Ref_count_base {
    private:
    	_Atomic_counter_t _Uses = 1;
    	_Atomic_counter_t _Weaks = 1;
    }
    
    

    很显然当所有的析构函数将 _Uses 降为 0 的时候就会对 i 进行释放,参考源码如下:

    
    class shared_ptr : public _Ptr_base<_Ty> {
    	~shared_ptr() noexcept { // release resource
    		this->_Decref();
    	}
    }
    
    class _Ref_count_base {
    
    	void _Decref() noexcept { // decrement use count
    		if (_MT_DECR(_Uses) == 0) {
    			_Destroy();
    			_Decwref();
    		}
    	}
    }
    
    

    原理大概摸清楚了,接下来看下循环引用,它最大的问题就是让 _Uses 永远也不会为 0,

    
    #include <iostream>
    #include <string>
    
    using namespace std;
    
    class BClass;
    
    class AClass
    {
    public:
    	int i = 10;
    	shared_ptr<BClass> b;
    };
    
    
    class BClass
    {
    public:
    	int i = 11;
    	shared_ptr<AClass> a;
    };
    
    void test() {
    
    	AClass* a = new AClass();
    	BClass* b = new BClass();
    
    	shared_ptr<AClass> aclass(a);
    	shared_ptr<BClass> bclass(b);
    
    	aclass->b = bclass;
    	bclass->a = aclass;
    
    	printf("a.refcnt=%d, b.refcnt=%d, a=%x,b=%x \n", aclass.use_count(), bclass.use_count(), &(*a), &(*b));
    }
    
    int main() {
    	test();
    }
    
    

    接下来可以在 main 方法结束的地方观察 a,b 所有的 heap 块的内存内容是否被释放? 可以清楚的看到还在那里。。. 如下图所示:

    这就是 循环引用 造成的问题,这时候就需要用到 weak_ptr 了。

    3. weak_ptr

    弱引用就是它的引用不改变对原对象中 _Uses 的引用,接下来将 AClass 和 BClass 中的 shared_ptr 改成 weak_ptr 版。

    
    class AClass
    {
    public:
    	int i = 10;
    	weak_ptr<BClass> b;
    };
    
    
    class BClass
    {
    public:
    	int i = 11;
    	weak_ptr<AClass> a;
    };
    
    

    从图中可以看到,此时 refcnt=1, 再观察下地址 0x007f71a8 的内容,可以发现已经被释放啦。

    好了,今天我们就聊这么多,希望对大家有帮助。

  • 相关阅读:
    第十六天
    第十五天
    STM8L段式液晶驱动器
    STM8L的LCD接口详解及驱动程序
    作为合格的工程师,这些电路图一辈子都得记住!
    双向晶闸管触发电路工作原理图
    3~15伏10A大电流可调稳压电源
    用TL431制作简单充电器电路
    5V USB充电器电路图
    555
  • 原文地址:https://www.cnblogs.com/huangxincheng/p/16514090.html
Copyright © 2020-2023  润新知