|
|
3
Dragon Energy
7 年前
< Buff行情>
我的问题是:RAII在多大程度上替代了其他设计模式?
像垃圾收集?我假设手动内存管理
不用于表示系统中的共享所有权。
< /块引用>
我不确定是否将其称为设计模式,但在我同样强烈的观点中,仅谈论内存资源,RAII几乎解决了GC可以解决的所有问题,同时引入更少的内存。
< Buff行情>
对象的共享所有权是否是不良设计的标志?
我也有这样的想法,即共享所有权在大多数情况下都是远远不够理想的,因为高级设计并不一定需要它。唯一一次我发现不可避免的是在持久数据结构的实现过程中,它至少被内部化为实现细节。
我发现gc或只是共享所有权的最大问题是,在应用程序资源方面,它不能免除开发人员的任何责任,但可以给人这样做的假象。如果我们有这样的情况(
scene
是该资源的唯一逻辑所有者,但是其他东西持有指向该资源的引用/指针,就像存储由用户定义的场景排除列表的相机,以忽略渲染):。
假设应用程序资源类似于图像,其生存期与用户输入有关(例如:当用户请求关闭包含该图像的文档时,应释放该图像),那么正确释放该资源的工作与使用或不使用GC时是相同的。
如果没有GC,我们可能会将其从场景列表中删除并允许调用其析构函数,同时触发一个事件以允许
thing1
,
thing2
,and
thing3
将其指针设置为空或从列表中删除它们,以便它们不具有悬空指针。
对于GC,基本上是一样的。我们在触发一个事件以允许
thing1>、
thing2
,and
thing3
将它们的引用设置为空或从列表中删除它们以便垃圾收集器可以收集它时,从场景列表中删除资源。
The silent programmer miss which flying under radar.
这种情况下的不同之处在于,当发生程序员错误时会发生什么,如
thing2->code>failing to handle the removal event。如果
thing2
stores a pointer,it now have a dangling pointer and we may have a crash.这是灾难性的,但是在我们的单元和集成测试中我们可能很容易捕捉到,或者至少QA或测试人员会很快捕捉到。我不在任务关键型或安全关键型环境中工作,因此,如果死机代码以某种方式成功发布,那么如果我们能够获得错误报告、复制它、检测它并很快修复它,这仍然不是那么糟糕。
如果
thing2->code>存储了一个强引用并共享所有权,那么我们会有一个非常安静的逻辑泄漏,并且在
thing2->code>被销毁之前,图像不会被释放(关闭之前它可能不会被销毁)。在我的领域中,这种沉默的错误性质是非常有问题的,因为即使在发货之后,它也会悄悄地被忽略,直到用户开始注意到在应用程序中工作一小时会导致它占用千兆字节的内存,例如,在重新启动之前,它会开始减速。在这一点上,我们可能已经积累了大量的这些问题,因为它们很容易像隐形战斗机昆虫一样在雷达下飞行,我最讨厌的就是隐形战斗机昆虫。
正是由于这种沉默的天性,我倾向于不喜欢热情地分享所有权,而且我从未理解为什么GC如此受欢迎(可能是我的特定领域——我承认我非常不了解那些关键任务的领域,例如),以至于我渴望没有GC的新语言。我发现调查与共享所有权相关的所有此类泄漏非常耗时,有时调查数小时,结果发现泄漏是由我们控制之外的源代码(第三方插件)引起的。
弱引用
对于我来说,弱引用在概念上非常适合于
thing1、
thing2和
thing3。这将允许他们在事后发现资源何时被破坏而不延长其生命周期,也许我们可以保证在这种情况下发生崩溃,或者有些人甚至可以在事后优雅地处理这一问题。对我来说,问题在于弱引用可以转换为强引用,反之亦然,因此在现有的内部和第三方开发人员中,仍有人可能粗心大意地将强引用存储在
thing2中
即使弱引用可能更为合适。
我过去确实尝试过在内部团队中尽可能多地鼓励使用弱引用,并记录应该在SDK中使用它。不幸的是,在这样一个广泛而复杂的群体中推广这种做法是很困难的,我们最终还是分享了我们的逻辑漏洞。
任何人在任何给定的时间,只要简单地在对象中存储一个对对象的强引用,就可以轻松地将对象的寿命延长得远远超过适当的时间,当我们俯视一个正在泄漏大量资源的巨大代码库时,这种情况开始变得非常可怕。我经常希望需要非常明确的语法来存储任何类型的强引用作为某类对象的成员,这类对象至少会导致开发人员对不必要的操作三思而后行。
显式销毁
因此,我倾向于对持久性应用程序资源进行显式破坏,比如:
关于移除事件:
//这对我来说很理想,不想释放一堆强大的
//引用并希望事物被隐式地销毁。
销毁(应用程序资源);
< /代码>
因为我们可以依靠它来释放资源。我们不能完全确信系统中的某些内容最终不会出现悬空指针或弱引用,但至少这些问题在测试中易于检测和重现。它们不会在很长一段时间内被忽视和积累。
对我来说,最棘手的问题就是多线程。在这些情况下,我发现有用的而不是全面的垃圾收集,或者说,
shared&ptr>code>,是简单地延迟销毁以某种方式:
关于移除事件:
//*可以*延迟到线程处理完资源。
销毁(应用程序资源);
< /代码>
在某些系统中,持久线程以某种方式被统一,使它们具有
处理
事件,例如,当线程没有被处理时,我们可以在时间片中以延迟的方式标记要销毁的资源(几乎开始感觉像停止世界GC,但我们保持显式销毁)。在其他情况下,我们可能会使用引用计数,但这样可以避免资源的引用计数从零开始并将使用上面的显式语法销毁,除非线程通过临时增加计数器而在本地延长其生存期(例如:在本地线程函数中使用作用域资源
正如看起来的那样,它避免了将gc引用或
shared-ptr->code>暴露给外部世界,这很容易吸引一些开发人员(在您的团队或第三方开发人员内部)将强引用(
shared-ptr->code>,例如)存储为对象的成员,如
thing2->code>从而扩展资源的LIFETIME不经意间,可能会持续很长时间(可能一直持续到应用程序关闭)。
raii
同时,raii自动消除物理泄漏和gc一样,但更进一步地说,它只适用于内存以外的资源。我们可以将它用于一个作用域互斥体,一个在销毁时自动关闭的文件,我们甚至可以使用它通过作用域保护等自动逆转外部副作用。
所以如果给了我选择,我必须选择一个,这对我来说很容易。我在一个领域工作,在这个领域中,由共享所有权引起的静默内存泄漏绝对是致命的,如果在测试的早期发现悬空指针崩溃(而且很可能会发生),那么悬空指针崩溃实际上是更好的选择。即使是在一些非常模糊的事件中,如果它在发生错误的站点附近发生崩溃,这仍然比使用内存分析工具并试图找出谁在翻阅数百万行代码时忘记释放引用要好。在我直截了当的观点中,GC引入的问题比它为我的特定领域(在场景组织和应用状态方面与游戏有点相似的vfx)解决的问题要多,除了这些非常无声的共享所有权泄漏之外,还有一个原因是它会给开发人员一个错误的印象,即他们不必精简k关于资源管理和持久性应用程序资源的所有权,同时无意中导致左右逻辑泄漏。
< Buff行情>
“RAII何时失效”
< /块引用>
在我的整个职业生涯中,我遇到过的唯一一个我想不出任何可能的方法来避免某种类型的共享所有权的情况是,当我实现了一个持久数据结构库时,就像这样:
我用它来实现一个不可变的网格数据结构,它可以修改部分而不被设置为唯一的,就像这样(用400万个四边形测试):
每一帧,一个新的网格正在创建,因为用户拖动它并雕刻它。不同的是,新的网格是强引用部分,不是由画笔使唯一的,这样我们就不必复制所有的顶点,所有的多边形,所有的边缘等。不变的版本琐碎了线程安全,例外安全,非破坏性编辑,撤销系统,实例等。
在这种情况下,不可变数据结构的整个概念都围绕着共享所有权,以避免复制不唯一的数据。这是一个真实的案例,我们不能避免共享所有权,无论发生什么(至少我想不出任何可能的方法)。
这是我们遇到的唯一可能需要GC或引用计数的情况。其他人可能也遇到过自己的问题,但根据我的经验,很少有情况真正需要在设计级别上共享所有权。
ment
不用于表示系统中的共享所有权。
我不确定是否将其称为设计模式,但在我同样强烈的观点中,仅仅谈论内存资源,RAII解决了GC可以解决的几乎所有问题,同时引入更少的内容。
对象的共享所有权是否是不良设计的标志?
我有一种想法,即共享所有权在
最
案例,因为高级设计不一定需要它。唯一一次我发现不可避免的是在持久数据结构的实现过程中,它至少被内部化为实现细节。
我发现gc或只是共享所有权的最大问题是,在应用程序资源方面,它不能免除开发人员的任何责任,但可以给人这样做的假象。如果我们有这样的案子(
Scene
是该资源的唯一逻辑所有者,但其他内容会保存指向该资源的引用/指针,就像相机存储用户定义的场景排除列表以忽略渲染一样):
假设应用程序资源类似于一个映像,其生存期与用户输入有关(例如:当用户请求关闭包含该映像的文档时,应该释放该映像),那么正确释放该资源的工作与使用或不使用GC是相同的。
如果没有GC,我们可以将其从场景列表中移除,并允许调用其析构函数,同时触发事件以允许
Thing1
,
Thing2
和
Thing3
将指向它的指针设置为空或从列表中删除它们,以便它们没有悬空指针。
对于GC,基本上是一样的。我们从场景列表中删除资源,同时触发一个事件以允许
TIGN1
,
TIGN2
和
三号
将它们的引用设置为空或从列表中移除它们,以便垃圾收集器可以收集它。
在雷达下飞行的无声程序错误
在这种情况下,不同之处在于当程序员出错时会发生什么,比如
TIGN2
无法处理删除事件。如果
TIGN2
存储一个指针,它现在有一个悬空指针,我们可能会崩溃。这是灾难性的,但是在我们的单元和集成测试中我们可能很容易捕捉到,或者至少QA或测试人员会很快捕捉到。我不在任务关键或安全关键的环境中工作,所以如果崩溃的代码以某种方式成功地发布,那么如果我们能够得到一个bug报告,复制它,检测它并很快地修复它,那就没有那么糟糕了。
如果
TIGN2
存储一个强大的引用并共享所有权,我们有一个非常安静的逻辑泄漏,并且在
TIGN2
被销毁(在关闭之前可能不会销毁)。在我的领域中,这种沉默的错误性质是非常有问题的,因为即使在发货之后,它也会悄悄地被忽略,直到用户开始注意到在应用程序中工作一小时会导致它占用千兆字节的内存,例如,在重新启动之前,它会开始减速。在那一点上,我们可能已经积累了大量的这些问题,因为它们很容易像隐形战斗机一样在雷达下飞行,我最讨厌的就是隐形战斗机。
正是因为这种沉默的天性,我才不喜欢共享的所有权,而且我也不明白为什么GC如此受欢迎(可能是我的特定领域——诚然,我对那些关键任务的领域非常无知,例如),以至于我渴望新的语言。
无气相色谱法
. 我发现调查与共享所有权相关的所有此类泄漏非常耗时,有时调查数小时,结果发现泄漏是由我们控制之外的源代码(第三方插件)造成的。
弱引用
弱引用在概念上对我来说是理想的
TIGN1
,请
TIGN2
,和
三号
. 这将允许他们在事后发现资源何时被破坏而不延长其生命周期,也许我们可以保证在这种情况下发生崩溃,或者有些人甚至可以在事后优雅地处理这一问题。对我来说,问题是弱引用可以转换为强引用,反之亦然,因此在现有的内部和第三方开发人员中,仍可能有人不小心将强引用存储在
TIGN2
即使是一个弱引用也会更合适。
我过去确实尝试过在内部团队中尽可能多地鼓励使用弱引用,并记录应该在SDK中使用它。不幸的是,在这样一个广泛而复杂的群体中推广这种做法是很困难的,我们最终还是有了逻辑上的漏洞。
任何人在任何给定的时间,只要简单地在对象中存储一个对对象的强引用,就可以轻松地将对象的寿命延长得远远超过适当的时间,当我们俯视一个正在泄漏大量资源的巨大代码库时,这种情况开始变得非常可怕。我经常希望需要一个非常明确的语法来存储任何类型的强引用,作为一个对象的成员,至少会导致开发人员对不必要的操作三思而后行。
显式破坏
因此,我倾向于对持久性应用程序资源进行显式破坏,比如:
on_removal_event:
// This is ideal to me, not trying to release a bunch of strong
// references and hoping things get implicitly destroyed.
destroy(app_resource);
……因为我们可以依靠它来释放资源。我们不能完全确信系统中的某些内容最终不会出现悬空指针或弱引用,但至少这些问题在测试中易于检测和重现。它们不会在很长一段时间内被忽视和积累。
对我来说,最棘手的问题就是多线程。在这些情况下,我发现有用的东西不是全方位的垃圾收集,或者说,
shared_ptr
,就是以某种方式推迟破坏:
on_removal_event:
// *May* be deferred until threads are finished processing the resource.
destroy(app_resource);
在某些系统中,持久线程以某种方式被统一,使它们具有
processing
事件,例如,当线程没有被处理时,我们可以在时间片中以延迟的方式标记要销毁的资源(几乎开始感觉好像停止了世界GC,但我们保留了显式销毁)。在其他情况下,我们可以使用引用计数,但要避免
SelddPPTR
,其中资源的引用计数从零开始,并将使用上面的显式语法销毁,除非线程通过临时增加计数器在本地延长其生存期(例如:在本地线程函数中使用作用域资源)。
正如看起来的那样,它避免公开GC引用或
共享资源
到外部世界,这很容易吸引一些开发人员(在您的团队或第三方开发人员内部)存储强引用(
SelddPPTR
例如)作为对象的成员
TIGN2
从而不经意地延长资源的生命周期,可能会比适当的时间长得多(可能一直到应用程序关闭)。
拉伊
同时,raii自动消除物理泄漏和gc一样,但更进一步地说,它只适用于内存以外的资源。我们可以将它用于一个作用域互斥体,一个在销毁时自动关闭的文件,我们甚至可以使用它通过作用域保护等自动逆转外部副作用。
所以如果给了我选择,我必须选择一个,这对我来说很容易。我在一个领域工作,在这个领域中,由共享所有权引起的静默内存泄漏绝对是致命的,如果在测试的早期发现悬空指针崩溃(而且很可能会发生),那么悬空指针崩溃实际上是更好的选择。即使是在一些非常模糊的事件中,如果它在发生错误的站点附近发生崩溃,这仍然比使用内存分析工具并试图找出谁在翻阅数百万行代码时忘记释放引用要好。在我直截了当的观点中,GC引入的问题比它为我的特定领域(在场景组织和应用状态方面与游戏有点相似的vfx)解决的问题要多,除了这些非常无声的共享所有权泄漏之外,还有一个原因是它会给开发人员一个错误的印象,即他们不必精简k关于资源管理和持久性应用程序资源的所有权,同时无意中导致左右逻辑泄漏。
“RAII何时失效”
在我的整个职业生涯中,我遇到过的唯一一个我想不出任何可能的方法来避免某种类型的共享所有权的情况是,当我实现了一个持久数据结构库时,就像这样:
我使用它来实现一个不可变的网格数据结构,它可以修改部分而不被设置为唯一的,就像这样(用400万个四边形进行测试):
每一帧,一个新的网格正在创建,因为用户拖动它并雕刻它。不同的是,新的网格是强引用部分,不是由画笔使唯一的,所以我们不必复制所有的顶点,所有的多边形,所有的边缘等。不变的版本琐碎了线程安全,例外安全,非破坏性编辑,撤消系统,实例等。
在这种情况下,不可变数据结构的整个概念都围绕着共享所有权,以避免复制不唯一的数据。这是一个真实的案例,我们不能避免共享所有权,无论发生什么(至少我想不出任何可能的方法)。
这是我们遇到的唯一可能需要GC或引用计数的情况。其他人可能也遇到过自己的问题,但根据我的经验,很少有情况真正需要在设计级别上共享所有权。
|