今天想记录一个东西所以登陆了下博客,发现居然有人评论。但是这篇博客满篇错误实在不忍直视。。。
为挽尊,尝试用c++智能指针解释一下这个问题。。。
假设:
class A {
shared_ptr<B> pb_;
}
class B {
shared_ptr<A> pa_;
}
调用方:
{
shared_ptr<A> pa(new A);
shared_ptr<B> pb(new B);
pa->pb_ = pb;
pb->pa_ = pa;
}
出了大括号以后,pa和pb生命期到期,让对象 A 和 B 的引用计数都降为1.
问题在于,成员变量 pa_ 和 pb_ 是没有办法减低引用计数的,
为什么呢,因为它们在对象 A 和 B 里面啊,也就是在堆上啊。
所以就只能互相引用到天荒地老。
为避免这个问题,c++中会将互相引用的两个对象中,其中一个引用 weak_ptr,
因为 weak_ptr 不会使引用计数加1,所以就不会出现这种互相拖着对方的事情了。
以下对比没有循环引用的情况:
class A {
shared_ptr<C> pc_;
}
class C {
}
调用方:
{
shared_ptr<A> pa(new A);
shared_ptr<C> pc(new C);
pa->pc_ = pc;
}
出了大括号以后,pa和pc生命期到期,让对象 A 的引用计数降为 0,C 的引用计数降为1.
然后堆上的 A 完成使命高高兴兴的析构,此时 pc_ 的生命期到期,使 C 的引用计数降为0,所以 C 也开开心心析构了。
。。。。。。。。。。。不知道这次有没有理解正确。。。。。。。。。。
===========================================羞耻的分界线=====================
。。。java鸟蛋表示搞不明白。。。
关于引用计数版本的垃圾回收机制,大家的说法都是:"无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0."
但是为什么不能检测出来呢?
比如
Class A
{
B b;
}
Class B
{
A a;
}
如果有以下代码:
{
A a = new A(); //假设分配了空间,地址为loc1,则loc1的引用计数为1;
B b = new B(); //假设分配了空间,地址为loc2,则loc2的引用计数为1;
a.b = b; //loc2的引用计数为2;
b.a = a; //loc1的引用计数为2;
}
变量a,b跳出作用范围之后,loc1和loc2的引用不会变成0吗?难道说,在对a进行处理时,不会将其成员b所引用的地址引用减1,同理,在处理变量b时,不会对其成员a所引用的地址减1,从而导致loc1和loc2的引用数目都为1?如果真的是这样的话,那不只是循环引用出问题,即使是没有循环引用也会出现问题的吧。比如:
Class A1
{
C1 c1;
}
Class C1
{
}
以下代码应用A1和C1:
{
A1 a1 = new A1(); //假设分配了空间,地址为loc1,则loc1的引用计数为1;
C1 c1 = new C1(); //假设分配了空间,地址为loc2,则loc2的引用计数为1;
a1.c1 = c1; //loc2的引用计数为2
}
则变量a1,c1跳出作用域后,如果变量a1并不对其成员c1所引用的地址减1,那么loc2的地址引用计数不是1吗?这样不会引起内存泄露?
========== update: 以下还是不要看了。。。 A 中的 b 只是一个指针而已,不要对人家要求太多啊。。。=========
所以我想,在变量跳出作用域后对其进行处理时,确实是要同时处理其成员的,而正是对其成员进行处理时,循环引用会导致一些问题。如果A和B互相引用,在a和b跳出作用域时,要处理a了,对其成员b引用的地址计数减1,要处理b了,对其成员a引用的地址计数减1,问题是既然处理顶层对象(偶表示用户自己定义的a和b)时需要考虑其成员引用的地址计数要减1,那么在对其成员进行处理时,应该也要遵循同样的规则,对成员的成员进行处理,我想这个时候就会造成无限的循环。
本来嘛,无限循环也没什么问题啊,就让计数变成0就不处理了嘛,但是关键是可能并非只有a引用b,很可能另外有c也引用b,那这个时候c还没有超出作用域但是b已经被垃圾回收了,那c不是悲剧了?
完全不知道理解的对不对,欢迎指正。