我只是在观看“ Going Native 2012”流,并且注意到有关的讨论std::shared_ptr
。听到Bjarne std::shared_ptr
对此持否定态度,以及他的评论是当物体的寿命不确定时应将其用作“最后手段”时,我感到有些惊讶(我认为,他认为这种情况很少见)。
有人愿意进一步解释这一点吗?我们如何std::shared_ptr
在没有安全的情况下进行编程并仍然可以安全地管理对象的生命周期?
我只是在观看“ Going Native 2012”流,并且注意到有关的讨论std::shared_ptr
。听到Bjarne std::shared_ptr
对此持否定态度,以及他的评论是当物体的寿命不确定时应将其用作“最后手段”时,我感到有些惊讶(我认为,他认为这种情况很少见)。
有人愿意进一步解释这一点吗?我们如何std::shared_ptr
在没有安全的情况下进行编程并仍然可以安全地管理对象的生命周期?
Answers:
如果您可以避免共享所有权,那么您的应用程序将更加简单易懂,因此不易受到维护期间引入的错误的影响。复杂或不清楚的所有权模型往往会导致难以通过可能难以跟踪的共享状态来跟踪应用程序不同部分之间的耦合。
鉴于此,最好使用具有自动存储持续时间的对象并具有“值”子对象。如果不这样做,unique_ptr
可能会是一个很好的选择,shared_ptr
即使不是万不得已,它也会在所需工具列表的下方。
Bjarne所生活的世界是一个……学术界,需要一个更好的期限。如果您可以对代码进行设计和结构化,以使对象具有非常深思熟虑的关系层次结构,以使所有权关系僵化且不屈服,则代码会朝一个方向(从高级到低级)流动,而对象仅与低级对象对话。层次结构,那么您将不需要太多shared_ptr
。在某些人不得不违反规则的罕见情况下,您会用到它。但是否则,您可以将所有内容粘贴在vector
s或使用值语义的其他数据结构中,并将unique_ptr
s用于必须单独分配的内容。
虽然这是一个生活的美好世界,但并非所有时间都能做到。如果您不能以这种方式来组织代码,因为您要尝试进行的系统设计意味着不可能(或者非常不愉快),那么您将发现自己越来越需要对象的共享所有权。
在这样的系统中,握着裸露的指针并不是……完全没有危险,但是确实会引发问题。最棒的shared_ptr
是,它为对象的生存期提供了合理的语法保证。可以打破吗?当然。但是人们也可以做到const_cast
。基本护理和喂养shared_ptr
应为必须共享所有权的分配对象提供合理的生活质量。
然后,有weak_ptr
s,如果没有,则不能使用shared_ptr
。如果您的系统是严格结构化的,则可以安全地存储指向某个对象的裸指针,前提是应用程序的结构可确保所指向的对象比您更长寿。您可以调用一个返回指向某个内部或外部值的指针的函数(例如,找到名为X的对象)。在结构正确的代码中,只有保证对象的寿命超过您的寿命,该功能才可用。因此,将裸指针存储在您的对象中就可以了。
由于是刚性并不总是能够在实际系统中实现,你需要一些方法来合理保证寿命。有时,您不需要完全所有权;有时,您只需要能够知道指针是好还是坏。那是weak_ptr
进入的地方。在某些情况下,我可以使用unique_ptr
or boost::scoped_ptr
,但是我必须使用a,shared_ptr
因为我特别需要给某人一个“易失”的指针。一个指针的生命周期是不确定的,并且他们可以查询该指针何时被销毁。
当世界状况不确定时,一种安全的生存方式。
可以通过某些函数调用来获取指针,而不是通过via来完成weak_ptr
吗?是的,但是更容易打破。返回裸指针的函数无法从句法上暗示用户不要做类似于长期存储该指针的操作。返回a shared_ptr
也使某人过于简单地简单地存储它并可能延长对象的寿命。weak_ptr
但是,返回a 强烈表明,存储shared_ptr
您的lock
来信是一个……可疑的主意。它不会阻止您执行此操作,但是C ++中的任何操作都不能阻止您破坏代码。weak_ptr
提供了一些抵制自然行为的最小阻力。
现在,这并不是说shared_ptr
不能滥用;当然可以。特别是pre- unique_ptr
,在很多情况下我只是使用a,boost::shared_ptr
因为我需要传递一个RAII指针或将其放在列表中。没有移动语义和unique_ptr
,boost::shared_ptr
是唯一的实际解决方案。
您可以在完全没有必要的地方使用它。如上所述,适当的代码结构可以消除对的某些使用需求shared_ptr
。但是,如果您的系统无法按原样进行构建,但仍然可以按需进行,shared_ptr
则将具有很大的用途。
shared_ptr
非常适合将c ++与脚本语言(例如python)集成在一起的系统。使用boost::python
,在c ++和python端的引用计数协作很大;来自c ++的任何对象仍可以保留在python中,并且不会死。
shared_ptr
。两者都使用自己的的实现intrusive_ptr
。我之所以提出这一点,是因为它们都是用C ++编写的大型应用程序的真实示例
shared_ptr
同样适用于intrusive_ptr
:他反对整个所有权的概念,而不是该概念的任何特定拼写。因此,出于这个问题的目的,这是两个确实使用的大型应用程序的真实示例shared_ptr
。(而且,更重要的是,它们证明,shared_ptr
即使它没有启用,它也很有用weak_ptr
。)
我不相信我曾经用过std::shared_ptr
。
大多数情况下,对象与某个集合相关联,在整个生命周期中它都属于该集合。在这种情况下,您可以仅使用whatever_collection<o_type>
或whatever_collection<std::unique_ptr<o_type>>
,该集合是对象或自动变量的成员。当然,如果您不需要动态数量的对象,则可以使用固定大小的自动数组。
通过集合进行迭代或对该对象进行任何其他操作都不需要帮助函数来共享所有权...它使用该对象,然后返回,并且调用方保证该对象在整个调用中均保持活动状态。到目前为止,这是主叫方和被叫方之间最常用的合同。
尼科尔·波拉斯(Nicol Bolas)评论说:“如果某个物体抓住裸露的指针,而该物体死亡……哎呀。” 和“对象需要确保该对象在该对象的生命中生存。只能shared_ptr
做到这一点。”
我不赞成这种说法。至少不能解决shared_ptr
这个问题。关于什么:
与垃圾回收一样,默认使用shared_ptr
鼓励程序员不要考虑对象之间或函数与调用者之间的约定。需要考虑正确的前提条件和后置条件,并且对象生存期只是更大的一块蛋糕中的一小部分。
对象不会“死亡”,某些代码会破坏它们。抛开shared_ptr
问题而不是弄清电话合同是错误的安全。
shared_ptr
,weak_ptr
旨在避免发生。Bjarne试图生活在一个世界中,即一切都有美好的,明确的一生,而一切都是以此为基础的。如果您可以建立这个世界,那就太好了。但是,事实并非如此。对象需要确保对象在对象的整个生命中都存在。只能shared_ptr
这样做。
shared_ptr
仅减轻了一项特定的外部修改,甚至没有最常见的修改。如果函数调用合同另有规定,则确保对象的生存期正确不是对象的责任。
unique_ptr
,表示只有一个指向该对象的指针存在并且它具有所有权。
shared_ptr
,则它仍应返回a unique_ptr
。从unique_ptr
到的转换shared_ptr
很容易,但是从逻辑上讲,反向转换是不可能的。
我宁愿不是绝对地思考(例如“不得已而为之”),而是相对于问题领域。
C ++可以提供许多不同的方法来管理生命周期。其中一些尝试以堆栈驱动的方式重新生成对象。其他一些尝试摆脱这种限制。其中一些是“文字的”,另一些是近似的。
实际上,您可以:
Person
具有相同的name
是同一人(最好:两个相同的表示人)。生命周期是由机器堆栈授予的,最终对程序无关紧要(因为一个人的名字,无论Person
它携带什么)std::unique_ptr
所做的(您可以认为它是大小为1的向量)。同样,您承认对象在它们所引用的数据结构之前(之后)开始存在(并结束它们的存在)。这种方法的缺点是,在执行更深层次的堆栈调用时,对象的类型和数量不能随创建位置的不同而变化。在对象创建和删除是用户活动的结果的所有情况下,所有这些技术都无法“发挥作用”,因此对象的运行时类型不是编译时已知的,并且可能存在引用对象的过度结构。用户要求从更深的堆栈级函数调用中删除。在这种情况下,您必须:
C ++ isteslf没有监视该事件(while(are_they_needed)
)的任何本机机制,因此您必须近似:
转到最后一个解决方案的第一个解决方案,随着组织和维护对象所花费的时间增加,管理对象生存期所需的辅助数据结构的数量也会增加。
垃圾收集器具有成本,shared_ptr较少,unique_ptr较少,并且堆栈管理的对象很少。
是shared_ptr
“最后的手段”吗?不,不是:不得已是垃圾收集器。
shared_ptr
实际上是std::
建议的最后选择。但是,如果您遇到我所解释的情况,则可能是最好的解决方案。
赫伯·萨特(Herb Sutter)在以后的会议中提到的一件事是,每当您复制一个副本时,都会shared_ptr<>
发生连锁的增量/减量。在多核系统上的多线程代码上,内存同步不是无关紧要的。给出选择后,最好使用堆栈值或a unique_ptr<>
并传递引用或原始指针。
shared_ptr
shared_ptr
仅仅因为它是标准解决方案,而使用像解决所有内存泄漏问题的灵丹妙药那样。这是一个诱人的陷阱,但是了解资源所有权仍然很重要,除非共享所有权,否则a shared_ptr<>
不是最佳选择。
我认为他的意思是,每个人只要写了标准指针(例如一种全局替换),就经常写shared_ptr,这已经成为一种普遍现象,并且它被用作替代品,而不是实际设计或至少规划对象的创建和删除。
人们忘记的另一件事(除了上述材料中提到的锁定/更新/解锁瓶颈),仅shared_ptr不能解决循环问题。您仍然可以使用shared_ptr泄漏资源:
对象A包含指向另一个对象A的共享指针。对象B创建A a1和A a2,并分配a1.otherA = a2; 和a2.otherA = a1; 现在,用于创建a1,a2的对象B的共享指针超出范围(例如,在函数末尾)。现在您有一个泄漏-没有其他人引用a1和a2,但是它们互相引用,因此它们的引用计数始终为1,因此您已经泄漏了。
那是简单的示例,当它在真实代码中发生时,通常会以复杂的方式发生。使用weak_ptr有一个解决方案,但是现在有很多人只在任何地方都执行shared_ptr,甚至都不知道泄漏问题,甚至都不知道weak_ptr。
总结一下:我认为OP引用的注释可以归结为:
无论您使用哪种语言(托管,非托管,还是介于中间的诸如引用共享之类的引用计数),都需要了解并有意识地决定对象的创建,生存期和销毁。
编辑:即使这意味着“未知,我需要使用shared_ptr”,您仍然会想到它,并且是有意这样做的。
我将尝试回答这个问题:
我们如何在没有std :: shared_ptr的情况下进行编程,并且仍然以安全的方式管理对象的生存期?
C ++具有大量不同的内存处理方式,例如:
struct A { MyStruct s1,s2; };
在类范围内使用而不是shared_ptr。这仅适用于高级程序员,因为它要求您了解依赖项的工作原理,并且需要能够充分控制依赖项以将其限制为树的能力。头文件中类的顺序是此方面的重要方面。似乎这种用法对于本机内置的c ++类型已经很普遍了,但由于这些依赖性和类顺序问题,似乎很少使用程序员定义的类。此解决方案也有sizeof问题。程序员将其中的问题视为使用前向声明或不必要的#include的要求,因此许多程序员将退回到劣等的指针解决方案,后来又转向shared_ptr。MyClass &find_obj(int i);
+ clone()代替shared_ptr<MyClass> create_obj(int i);
。许多程序员希望创建工厂来创建新对象。shared_ptr非常适合这种用法。问题在于它已经假定使用堆/空闲存储分配的复杂内存管理解决方案,而不是更简单的基于堆栈或对象的解决方案。良好的C ++类层次结构支持所有内存管理方案,而不仅仅是其中一种。如果返回的对象存储在包含对象内,而不使用局部函数作用域变量,则基于引用的解决方案可以工作。应避免将所有权从工厂传递给用户代码。使用find_obj()之后复制对象是处理它的好方法-带有复制参数的普通复制构造函数和普通构造函数(不同类)或多态对象的clone()都可以处理它。unique_ptr
最适合工厂。您可以将unique_ptr
变成shared_ptr
,但是从逻辑上讲不可能朝另一个方向发展。