酚醛保温板制作工艺
看了几篇别人写得文章,感觉写得模模糊糊的。。于是自己去读了读。
本文不仅精确而且完备。看了本文之后,即使自己实现一套智能指针也没有问题。
下文中
“智能指针”指shared_ptr或weak_ptr
“裸指针”指原始的C指针
裸指针还是智能指针通常是上下文自明的,此时直接用“指针”一词指代两者之一
首先,我们来看shared_ptr和weak_ptr持有了哪些数据。
对于shared_ptr和weak_ptr,都仅持有两个字段,指向对象地址的指针ptr,和指向计数器对象的指针rep。
对于计数器对象,我们仅需要关心两个字段,一个是弱引用计数weaks(无符号整型),一个是强引用计数uses(无符号整型)。
那么,若s1,s2是仅有的两个指向对象obj的shared_ptr,w1,w2,w3是仅有的三个指向对象obj的weak_ptr,则以上所有的智能指针(s1,s2,w1,w2,w3)的ptr字段保存着obj的地址,所有的rep字段指向同一个计数器对象(记为c1),c1的weaks字段现在是4(3+1,为什么加一后面会讲),uses字段现在是2。
此时,为了我写着方便,我们可以将s1和c1简记为 s1=(&obj,&c1);c1=(u:2,w:4)
指针中两个字段:ptr,rep。计数对象中两个字段:uses,weaks。请捋顺之后再向下阅读。
下面,我们来结合例子来讲每个操作背后发生了什么,注意:例子是连续的,即上一个操作的结束是下一个操作的开始。
操作1:构造新的空指针
shared_ptr
weak_ptr
初始化操作十分简单,s1,s2和w1的ptr和rep都被初始为了NULL,
记作s1=s2=w1=(NULL,NULL)
操作2:使用裸指针构造智能指针
int* a=new int(1);
int* b=new int(2);
s1.reset(a);
发生条件:用裸指针reset智能指针,或将裸指针作为参数调用构造函数
执行过程:
//创建一个新的计数器对象(记为c1),计数器对象的weaks和uses都初始为1
new c1=(u:1,w:1);
// 将s1的ptr赋值为裸指针a,将s1的rep指向c1
s1=(a,&c1);
操作3:从强引用构造智能指针
s2=s1; // uses计数+1
w1=s1; // weaks计数+1
weak_ptr
发生条件:用shared_ptr构造智能指针或赋值空的智能指针。用weak_ptr构造weak_ptr或赋值空的weak_ptr。
执行过程:
// 若右值的rep非空,则复制右值的rep到左值,然后将rep的uses或weaks计数原子+1
if(s1.rep&&s1.rep->uses或weaks计数原子+1)
s2.rep=s1.rep;
s2.ptr=s1.ptr;
结果:
c1=(u:2,w:3);s1=s2=w1=w2=(a,&c1)
以下结论始终成立:
uses计数=实际强引用数。
weaks计数=
1.实际弱引用数+1(当uses计数不为0)
2.实际弱引用数(当uses计数为0)
如果uses计数不为0,则weaks计数至少为1
操作4:从弱引用构造强引用
shared_ptr
发生条件:用weak_ptr,构造或赋值空的shared_ptr。执行weak_ptr的lock函数
执行过程:
if(w1.rep){
将w1.rep->uses字段加乐观锁{
if(w1.rep->uses!=0){
w1.rep->uses++;
s3.rep=w1.rep;
s3.ptr=w1.ptr;
}else{
抛出bad_weak_ptr异常
}
}
}
结果:
c1=(u:3,w:3);s1=s2=s3=w1=w2=(a,&c1)
操作5:释放弱引用
w1.reset
发生条件:1.对weak_ptr做reset。2.见“一些组合操作”
执行过程:
w1.ptr=NULL;
w1.rep=NULL;
if(w1.rep->原子操作weaks-1并返回新的值==0){
释放对象 w1.rep;
}
操作6:释放强引用
s1.reset
发生条件:1.对shared_ptr做reset。2.见“一些组合操作”
执行过程:
s1.ptr=NULL
s1.rep=NULL
f(s1.rep->原子操作uses-1并返回新的值==0){
对s1的rep额外执行一次释放弱引用操作;
释放对象s1.ptr;
}
// 如果uses为0,weaks计数等于实际弱引用数,如果uses不为0,weaks计数等于实际弱引用数+1。因此如果users不为0则weaks至少为1。
操作7:一些组合操作和例子
此时,s2=s3=w2=(a,&c1); c1=(u:2,w:2)
操作:s2.reset(b);
相当于执行“强引用释放操作”: s2.reset,然后再执行“从裸指针初始化”操作 s2.reset(b)
操作完成后,此时:
s3=w2=(a,&c1); c1=(u:1,w:2); s2=(b,&c2);c2=(u:1,w:1)
操作:s3=s2
相当于执行“强引用释放操作”: s3.reset,然后执行“从强引用构造智能指针”操作:s3=s2
此时
w2=(a,&c1); c1=(u:0,w:1);s1=s2=(b,&c2);c2=(u:2,w:1)
注意:s3.reset的过程中,因为c1的uses减为0。所以 delete a操作被执行,c1的weaks被额外-1。此时w2的ptr值依然等于指针a,但是delete a已经被执行,所以ptr=a是个野指针。但我们不会访问到这个野指针,因为“从弱引用构造强引用”这个操作会检查uses的计数,并抛出bad_weak_ptr异常。
操作:w2.reset
这个操作是释放弱引用。举这个例子是想说,c1的weaks计数只剩下1了,此时执行这个操作将使c1的weaks计数减为酚醛保温板制作工艺0并释放c1。也标志着所有指向a的强/弱引用全部释放完毕。正如我反复强调的一样,如果uses不为0,即强引用不为0,则weaks计数至少为1。weaks为0则强弱引用的数量必然都为0。
另外,考虑一种情况。设此时有 w1=(a,&c1); c1=(u:0,w:1);w2=(NULL,NULL)
如果一个线程p1执行w1.reset 。同时线程p2执行w2=w1
这是不安全的。考虑下面这种执行顺序
p2: w2=w1 执行至 检查完成w1的rep非空,但是尚未执行引用计数的增加。
p1:w1.reset 完全执行,此时weaks计数归0,w1将rep所指向的c1释放。
p2:w2=w1 继续执行。它将使用野指针w1.rep执行引用计数+1操作,并复制野指针rep和ptr。
当p1线程写w1时,p2读了w1。这是犯规的。智能指针的线程安全性仅限于:
多线程持有不同的智能指针对象,此时并发的操作这些对象是线程安全的。即使他们引用的计数器对象是同一个。
多线程并发的读同一个智能指针对象是安全的。
当一个线程正在修改一个智能指针对象时,其他线程对这个智能指针进行读和写都是不安全的。
主要操作的执行过程就讲完了。
这是酚醛发泡保温板厂家 16:28:12)
评论(0)