免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
123下一页
最近访问板块 发新帖
查看: 7280 | 回复: 25
打印 上一主题 下一主题

删帖吧 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2011-07-26 16:56 |只看该作者 |倒序浏览
本帖最后由 xyfree 于 2012-01-21 03:31 编辑

论坛徽章:
0
2 [报告]
发表于 2011-07-26 16:57 |只看该作者
本帖最后由 xyfree 于 2012-01-21 03:31 编辑

论坛徽章:
0
3 [报告]
发表于 2011-07-26 17:11 |只看该作者
不是太明白,weak_ptr 就是用来解决这种问题的

论坛徽章:
0
4 [报告]
发表于 2011-07-26 19:29 |只看该作者
本帖最后由 xyfree 于 2012-01-21 03:32 编辑

论坛徽章:
0
5 [报告]
发表于 2011-07-27 15:46 |只看该作者
回复  int-main


    具体的过程已经写完了。你可以参考一下和shared_ptr weak_ptr 有何不同,然后具体 ...
xyfree 发表于 2011-07-26 19:29



   
好吧,我看的时候你的帖子才写一半,现在看来真的是一个好贴。

版主呢,这样的帖子不加精就说不过去了!

论坛徽章:
5
狮子座
日期:2013-08-20 10:12:24午马
日期:2013-11-23 18:04:102015年辞旧岁徽章
日期:2015-03-03 16:54:152015亚冠之德黑兰石油
日期:2015-06-29 18:11:1115-16赛季CBA联赛之新疆
日期:2024-02-21 10:00:53
6 [报告]
发表于 2011-07-28 10:01 |只看该作者
想提两个问题:
1. 通常采用了引用计数的资源都是不会有“作用域”一说的,它仅仅能被指向他的指针(或者智能指针)访问,而众所周知,智能指针就算超出作用域,只要其引用计数不为1也是不会调用资源的析构函数的。那么请问,既然是这样,您在第一个例子里面指出的“当resource_t超出作用域,其析构函数会被调用“到底是什么情况?注意这时任何一个可能超出作用域的智能指针的值都在1以上。

2. 你的第二个例子说,aptr超出作用域后悔触发 TypeA 的析构函数,而这会触发 bptr 的引用减一,然而这是不可能的。因为这时aptr的引用计数为2,而你先判断引用计数,再减一,因哟个计数不为一所以对应的资源(这里是TypeA)不可能被析构,也不可能有 TypeA::~TypeA 被调用,从而 bptr 的引用也不会减一。另外C++会以堆栈的反方向去析构对象,所以这里应该是bptr的析构函数先被调用。能具体解释一下这里么?

论坛徽章:
0
7 [报告]
发表于 2011-07-28 12:44 |只看该作者
本帖最后由 xyfree 于 2012-01-21 03:32 编辑

论坛徽章:
5
狮子座
日期:2013-08-20 10:12:24午马
日期:2013-11-23 18:04:102015年辞旧岁徽章
日期:2015-03-03 16:54:152015亚冠之德黑兰石油
日期:2015-06-29 18:11:1115-16赛季CBA联赛之新疆
日期:2024-02-21 10:00:53
8 [报告]
发表于 2011-07-28 13:16 |只看该作者
本帖最后由 starwing83 于 2011-07-28 13:20 编辑

在和你讨论之前要强调一点:自动指针本身的目的就是为了简化资源的管理。在析构函数里面去显式释放掉指针引用不如一开始就别用自动指针。特别是mptr这种指针无法产生弱引用效果(即,无法在所有强引用消失后,自动清除掉弱引用),所以直接用一个普通的指针是一样的效果。想象一下用auto_ptr的类,它们绝对不会在析构函数去处理这些指针的。

这里说一下,我这么说并不是否认你的方法,你这样类似于C#的两步释放。第一步断掉引用,第二步回收资源。

对于boost::shared_ptr,实际上引用计数也在对象身上,而不是在指针身上,这是肯定的。因为引用本身是不会被计数的,被计数是的引用的资源。

回复1:

   说得对,resource_t 是不会有作用域的 resource_t 类的对象,在很多上下文中也不适用“作用域”这个概念。
   但是,我说的是 resource ,在代码中,resource 的定义是一个 mptr,至少编译器就是这么理解的,所以它是有作用域的。
   当然,你可以责怪我起名字起得太令人迷惑了……

   所以,当 resource 作为一个mptr 是会退出作用域的,当然也就会自动调用其析构函数,函数的代码已经贴出来了,很简单,就是直接调用 resource_t 的析构函数(我为变量起的名的确很不好, resource 不是 resource_t 类的对象!!)
   所以 resource(一个智能指针) 所引用的资源,引用计数就减一了。
xyfree 发表于 2011-07-28 12:44

你说的对,引用计数减一了,但是这个时候一个resource_t资源(而不是一个指针)本身的计数值是2,一个是resource指针的引用,一个是resource_t内部的那个引用。而在断掉了resource指针的引用,resource_t资源的引用才会变成1,这无法触发resource_t的析构函数被调用:注意一个很重要的概念,并不是每个指针被析构都必须调用资源的析构函数的,这会导致你完全没有使用引用计数的必要了。在这里,因为resource_t的资源在resource指针被析构之后才会被减一成为1,resource_t资源的析构函数理论上是完全不可能被调用的。

回复2:

   道理是同上的,aptr 超出作用域之后,直接就由 作为智能指针的 aptr 直接调用 TypeA::~TypeA(),这是没什么疑问的吧?
xyfree 发表于 2011-07-28 12:44

有,很有,我所有的疑问都在这里。上面说了,我认为aptr的析构决不可能导致TypeA的析构函数被调用,因为TypeA身上的引用计数在aptr析构不是1。

   而 TypeA::~TypeA() 会对其所持有的所有资源进行释放,所谓释放,具体来说就是,对所有类型为 mptr 的成员赋一个空指针值。
   所以 aptr 所持有的资源引用计数会减一,这是因为 TypeA::~TypeA() 被调用了;而bptr 所持有的资源引用计数会减一,这是因为 refB 被赋了一个空值。

xyfree 发表于 2011-07-28 12:44

你的思路是对的,但是实现手法不对,为什么呢?因为这里涉及到了一个先有鸡还是先有蛋的问题。在C#里面,两步析构的前提是用户手动调用对象的dispose,而不是调用指针的某个函数。这就是一个引子,而你的实现里面没有这种引子。你寄希望于资源的析构函数被调用,然而在引用计数确实被减到1前,资源的析构函数是不可能被调用的,然而析构函数不被调用引用计数就不可能减到1,这就是为什么循环引用会导致资源泄漏的主要原因,而你没有解决这个原因。

请留意我使用的字眼吧:

我不会使用 “aptr 的引用计数被减一” 这样的说法。
而是,“aptr 所持有的资源引用计数被减一”。

这样或许会有助于你理清思路。

我特别强调了,析构函数要正确地编写。
而所谓正确地编写,很多情况下都是“对象中的mptr类型的成员都赋一个空指针值”。
xyfree 发表于 2011-07-28 12:44



重复一下,所有的引用计数都是针对对对象本身的引用的,而这种信息是不会保存在引用身上的。

总结一下我的观点:
1. 你的代码无法做到解锁循环引用。原因是你要解锁循环引用就依赖资源本身的析构函数被调用,然而在该资源引用数大于1时这是不可能的。
2. 你想做到两步析构,但是两步析构必须要有用户直接参与,在C#里面是手动去调用对象的dispose,仅仅通过一个指针无法自动地做到这一点。
3. 单纯依靠引用无法完全解决循环引用问题。解决循环引用的关键是确定在某个点,一个对象确实可以被回收,然后在这个时间点就可以去调用对象本身的dispose断掉引用环,从而释放掉该对象。这也是C#采取的方式。

补充一点:这里的关键是对对象本身操作,而不是对指针操作。C++的自动析构机制也无法为此提供帮助,因此说C#没有自动析构只有垃圾回收是不着重点的。



【UPDATE】 写的很急,修改了一些措辞避免引起误会。

论坛徽章:
0
9 [报告]
发表于 2011-07-28 17:12 |只看该作者
本帖最后由 xyfree 于 2012-01-21 03:32 编辑

论坛徽章:
5
狮子座
日期:2013-08-20 10:12:24午马
日期:2013-11-23 18:04:102015年辞旧岁徽章
日期:2015-03-03 16:54:152015亚冠之德黑兰石油
日期:2015-06-29 18:11:1115-16赛季CBA联赛之新疆
日期:2024-02-21 10:00:53
10 [报告]
发表于 2011-07-28 18:28 |只看该作者
本帖最后由 starwing83 于 2011-07-28 18:32 编辑
回复  starwing83


赞同你的分析,我确实低估了自动管理资源的难度。
不过我还是想到有方法可以做到, ...
xyfree 发表于 2011-07-28 17:12



    还不如显式定义“回引”指针,即弱引用指针,会简单很多。

你的思路是想检测出“回引”。这涉及到两个方面:
1. 得到某个指针的“父容器”。
2. 判断该指针的指向是否会指向此“父容器”。

由于知道了父容器的大小,因此可以很容易判断指针“物理上”是不是在父容器内,但是你考虑过这种情况吗?

struct TypeA
{
    TypePrivate* p_;
};

struct TypePrivate {
    mptr<TypeB> bptr_;
};

意思你肯定懂,这里涉及到“物理包含”和“逻辑包含”,除非你强制用户仅仅使用mptr一种指针,否则你无法检测到所有的回环。到此为止,复杂度就已经超过了弱引用的解决方案了。但是也还有一些探讨的意义,不过已经超出了设计的范畴,变得有些tricks了。

对于这个问题,是有两套优美而又简洁的解决方案的:
- 其一,如果我们仅仅用指针代表某种依赖关系,而我们确实知道某些对象什么时候需要被销毁(这种情形是很常见的),那么我们就可以实现对象的二次析构。非常简单,向mptr添加一个destroy函数,使其销毁指向的资源,并且清除所有指向的引用。这类似于C#的二次析构。但是这需要保证对象在其他地方的确没有引用。C#不能保证这一点,但C#是垃圾回收的,如果有引用则对象不会被回收,这保证了安全。在C++里面,我们让资源有个函数叫detach,让其自己断掉引用。然后这个对象就会被“自然地”回收掉。这涉及到对对象本身有限制,但是你会发现这是有用的。

- 其二,如果我们在给定指针时提供某些关于回环的有用信息,则实现自动资源管理会简单的多。最简单的信息莫过于告诉系统:“这里有回环”。从这个思路出发得到的结果就是弱引用机制。其实弱引用的设计是十分优美的:每个对象除了维护一个计数以外,还维护一个弱引用组成的链表。当对象被销毁,则所有弱引用链表上的指针全部被清空。这是十分优美的。我认为这也是十分直接和高效的。使用这套机制需要对弱引用有足够的了解,而我刚才的叙述已经足够。这其实相当于自动地做了上面“detach”的事情。这里引入了一个策略:“没有强指针引用即意味着需要detach,detach的默认实现是清空弱引用表每一项指向的值”。

所以你可以看出来,弱引用机制不是随意实现的,它实际上还是经历过了类似于你所做的思考的。

在自动判断回环上也存在一些可能性。这个我还没想过。你可以先想想,不过首先你得绕过我刚才提到的“逻辑回环”的问题。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP