上一篇文章 C++:智能指针(1)——auto_ptr, unique_ptr的区别已经介绍了auto_ptr与unique_ptr;如果有疑惑的同学可以查看;
shared_ptr
shared_ptr是原始指针的封装类。它是一个附加了引用计数所有权模型,即它与shared_ptr的所有副本协作维护其包含的指针的引用计数。因此,每当一个新的指针指向资源时,计数器就会增加,而在调用对象的析构函数时,计数器就会减少。
引用计数一种用于存储对资源(例如对象,内存块,磁盘空间或其他资源)的引用,指针或句柄数量的技术。 在引用计数大于零之前,即直到删除了shared_ptr的所有副本之前,原始指针指向的堆内存不会被释放。 因此,当我们要将一个原始指针分配给多个所有者时,应该使用shared_ptr。
源码
template<class _Tp>
class _LIBCPP_TEMPLATE_VIS shared_ptr
{
public:
typedef _Tp element_type;
#if _LIBCPP_STD_VER > 14
typedef weak_ptr<_Tp> weak_type;
#endif
private:
element_type* __ptr_;
__shared_weak_count* __cntrl_;
...
}
析构函数
template<class _Tp>
shared_ptr<_Tp>::~shared_ptr()
{
if (__cntrl_)
__cntrl_->__release_shared();
}
可以看到当计数为0时才释放;
shared_ptr定义了复制构造函数与赋值运算符,所以可以放入STL容器;shared_ptr的赋值不会像auto_ptr导致所有权的转换,而是增加了一个所有权的共享;
shared_ptr的循环引用问题
Circular reference关系(shared_ptr的问题):让我们考虑一个场景,其中我们有两个类A和B,它们都具有指向其他类的指针。因此,总是像A指向B,B指向A。因此,use_count永远不会达到零,也永远不会被删除。
图转自https://www.geeksforgeeks.org/auto_ptr-unique_ptr-shared_ptr-weak_ptr-2/
//
// 智能指针shared_ptr循环引用.cpp
// LeetCodePlayground
//
// Created by 兆吉 王 on 2020/4/15.
// Copyright © 2020 兆吉 王. All rights reserved.
//
#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\n";
}
};
typedef std::shared_ptr<Child> ChildPtr;
class Parent {
public:
ChildPtr son;
Parent() {
cout << "hello parent\n";
}
~Parent() {
cout << "bye Parent\n";
}
};
void testParentAndChild()
{
/*
均用shared_ptr指向了new出的对象
shared_ptr<Parent> noramlp,p 与 shared_ptr<children> c 均在栈上
对应的new内存 均在堆上
*/
ParentPtr normalp(new Parent());
ParentPtr p(new Parent());
ChildPtr c(new Child());
p->son = c;
c->father = p;
cout << normalp.use_count() << endl;
cout << p.use_count() << endl;
cout << c.use_count() << endl;
/*
当程序返回时
shared_ptr<Parent> noramlp,p 与 shared_ptr<children> c 栈上的变量被释放
noramlp的释放导致了对应堆上内存的 引用计数 为0,
所以释放 ParentPtr normalp(new Parent()); new出来的内存,调用了Parent的~Parent析构函数
p与c同样被栈释放,但是对应堆上内存的 引用计数 为1,
所以未调用析构函数
*/
}
int main()
{
testParentAndChild();
return 0;
}
输出
hello parent
hello parent
hello Child
1
2
2
bye Parent
Program ended with exit code: 0
当程序返回时
shared_ptr<Parent> noramlp,p
与 shared_ptr<children> c
栈上的变量被释放
noramlp的释放导致了对应堆上内存的 引用计数 为0,
所以释放 ParentPtr normalp(new Parent());
new出来的内存,调用了Parent的~Parent析构函数
p与c同样被栈释放,但是对应堆上内存的 引用计数 为1,
所以p与c的释放无法使对应堆内存的引用计数归0(循环引用),导致两块内存无法释放。可以看到程序结束时并未调用两块内存的析构函数;
weak_ptr
创建weak_ptr作为shared_ptr的副本。它提供对一个或多个shared_ptr实例所拥有但不参与引用计数的对象的访问。 weak_ptr的存在或破坏对shared_ptr或其其他副本没有影响。在某些情况下,需要在shared_ptr实例之间中断循环引用。
图转自https://www.geeksforgeeks.org/auto_ptr-unique_ptr-shared_ptr-weak_ptr-2/
typedef std::weak_ptr<Parent> WeakParentPtr;
class Child
{
public:
WeakParentPtr father; // 只要一环换成 weak_ptr, 即可打破环
Child() {
cout << "hello Child" << endl;
}
~Child() {
cout << "bye Child\n";
}
};
修改类内智能指针为weak_ptr后,输出:
hello parent
hello parent
hello Child
1
2
bye Parent
bye Parent
bye Child
Program ended with exit code: 0
weak_ptr存在的意义
weak_ptr 是为了辅助shared_ptr而引入的一种智能指针,它存在的意义就是协助shared_ptr更好的完成工作,我们可以把它比做成一个秘书或助理;
weak_ptr的构造和析构并不会改变引用计数的大小,它可以由一个shared_ptr或weak_ptr的对象构造获得。它没有对“*”和“->”的重载,但可以使用lock获得一个可用的shared_ptr对象
参考链接
https://www.geeksforgeeks.org/auto_ptr-unique_ptr-shared_ptr-weak_ptr-2/