讲解make_shared()
示例1:
**这种做法没有意义!**我们从堆区构建一个对象Object,地址给给指针op,当我们做共享指针pobja,pobjb后,会导致这个Object对象被重复释放。导致内存被重复释放。
在以后编程中,如果使用智能指针就不要再去使用裸指针
我们构建共享型智能指针有两种方式
两种方法都是从堆区new构建对象
make_shared()的方法的特点是:调动Object的构造函数,把括号里的值作为其构造函数的实参,去构建对象
1、用原生指针去创建
要创建2次,第1次去创建Object这个对象,shared_ptr有一个指针指向Object对象。shared_ptr还要有一个指针去指向引用计数。第2次去创建引用计数。引用计数有两部分构成:观察者引用计数(mCnt_w)和真实拥有者引用计数(mCnt_s)。
也就是说,我们用普通的方式去创建,采取的就是上图创建方式。
内存在构建时构建2次,释放的时候也得释放2次。
如果我们没有weak_ptr智能指针,mCnt_w=0,我们在构建pObj对象,new2次空间,一次指向Object对象,一次指向引用计数,当没有任何对象去共享Object对象的时候,引用计数为0,我们把Object对象释放,我们把引用计数释放,分2次释放。
如果我们还有去构建weak_ptr智能指针
weak_ptr也指向了引用计数,观察者引用计数变为1,当真实拥有者(mCnt_s=0),pObj对象要销毁时,只能把Object对象销毁,不能去释放引用计数对象,只有当弱智能指针销毁时,观察者引用计数变为0,才能去销毁释放引用计数对象。
2、用make_shared()方法构建
特点是同时分配好引用计数和对象。shared_ptr的两个指针,一个指向引用计数,一个指向Object对象。
对内存只进行一次操纵!
任何的计数方法,我们应该考虑是否适合当时的环境,场景,而不去讨论这有什么优点,那有什么缺点
下面这种写法,会出现什么情况?
这里的步骤:第一步:构建Object对象,第二步:调动couldThrowException函数,第三步构建共享型智能指针,
**如果我们第一步构建完Object对象,执行第二步couldThrowException函数可以正常执行,**没有抛出异常,返回12.23,接着构建智能指针,此智能指针的一个指针指向Object对象,一个指针指向引用计数,引用计数为1,引用计数里的一个指针指向Object对象,构建完成之后,调动doSomething函数,把12.23给d,把无名对象给pt,调动移动构造。然后程序向下执行。没问题。
**如果是我们第一步构建完Object对象,执行第二步couldThrowException函数抛出异常,**造成doSomething函数在调动时抛出异常,此函数就不调动了,我们的共享型智能指针就没有产生,我们的Object对象就没有被销毁,造成了我们的内存泄漏
对象并没有被析构掉!
所以我们使用make_shared()使得第一步和第三步挨着一起
不存在内存的泄漏!
make_shared()把对象和引用计数放在同一个空间,同一时间去创建
如果我们Object对象的空间过大,如果使用make_shared来管理内存,当shared_ptr生存期到了,真实拥有者为0,析构Object对象,但是并没有回收这部分空间。只有当weak_ptr智能指针销毁时(生存期到达了),观察者引用计数为0,整个对象空间才可以被释放
对象很大,相当于有很大一块内存被无意义地锁住一段时间。
因为整个空间是一次分配好的,堆内存管理,不能从中间开始释放
这种做法是错误的!
缩减指针的正确做法
示例:
运行结果如下
增加代码
wp生存期不到,objlist.front()就不销毁
实际上空间并没有释放
头结点不做赋值。我们执行objlist.pop_front(),把第一个结点删除了,真实拥有者引用计数为0,把Object对象也析构了,但是观察者引用计数为1,堆区的一整块空间并没有被释放。
auto,unique_ptr
auto:依次访问容器的元素,x是容器里的类型(唯一型智能指针)
如果编写成下面这种情况
加上引用&,引用list每一个元素,x就是一个一个元素的别名。auto的特点是可以推断出元素的类型(唯一性智能指针)
如果没有&,要把唯一性智能指针的值拷贝起来给x,但是唯一性智能指针的拷贝构造函数是被删除的。导致容器里的智能指针没有办法进行拷贝给x
下面这种做法是可以的(转移拥有权)
修改代码如下
运行程序,程序崩溃
修改代码如下
程序正常运行
是因为重载了bool()