一:背景
上一篇我们聊到了C++ 的 auto_ptr
,有朋友说已经在 C++ 17
中被弃用了,感谢朋友提醒,今天我们来聊一下 C++ 11
中引入的几个智能指针。
unique_ptr
shared_ptr
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 将 =,复制构造函数
都给屏蔽了,所以再硬写的话,必然是报错。
![54ab8c217f80941a32630c6cade2f4c5.png](https://i-blog.csdnimg.cn/blog_migrate/59335427df3f82e16f03b725e988b069.png)
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();
}
![3124293e2b0fb48e8a18e67b87068d11.png](https://i-blog.csdnimg.cn/blog_migrate/869cabac08e65459c0996153c2a26a5f.png)
再看下源码,大概在内部共同维护了对 _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();
}
![9e6dab58b474dfbb200b21c93a8c8c07.png](https://i-blog.csdnimg.cn/blog_migrate/0b286cc1252f8433079c242a1b187527.png)
接下来可以在 main 方法结束的地方观察 a,b
所有的 heap 块的内存内容是否被释放? 可以清楚的看到还在那里。。. 如下图所示:
![af0dda1158fef2e027e28d878da1ba19.png](https://i-blog.csdnimg.cn/blog_migrate/850d291e00f15c778399e573852898a7.png)
这就是 循环引用
造成的问题,这时候就需要用到 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;
};
![061db3d044d4116c8e8d09057497194b.png](https://i-blog.csdnimg.cn/blog_migrate/dc2ff3a5d1ccff3e7ee83a7baafcc93b.png)
从图中可以看到,此时 refcnt=1
, 再观察下地址 0x007f71a8
的内容,可以发现已经被释放啦。
![ee4d161a9811c70657014a6c4f33b92d.png](https://i-blog.csdnimg.cn/blog_migrate/9b4951c75d6327570294d11cf54d5af2.png)
好了,今天我们就聊这么多,希望对大家有帮助。