共享指针如何知道有多少指针指向该对象? (在这种情况下为shared_ptr)
答案 0 :(得分:59)
基本上,shared_ptr
有两个指针:指向共享对象的指针和指向包含两个引用计数的结构的指针:一个用于“强引用”,或者是具有所有权的引用,另一个用于“弱引用” ,“或没有所有权的参考文献。
复制shared_ptr
时,复制构造函数会增加强引用计数。当你销毁shared_ptr
时,析构函数会减少强引用计数并测试引用计数是否为零;如果是,则析构函数删除共享对象,因为没有shared_ptr
指向它。
弱引用计数用于支持weak_ptr
;基本上,每当从weak_ptr
创建shared_ptr
时,弱引用计数就会递增,并且任何时候一个被销毁,弱引用计数就会递减。只要强引用计数或弱引用计数大于零,引用计数结构就不会被销毁。
实际上,只要强引用计数大于零,就不会删除共享对象。只要强引用计数或弱引用计数不为零,就不会删除引用计数结构。
答案 1 :(得分:16)
我普遍同意James McNellis的回答。但是还有一点需要提及。
如您所知,shared_ptr<T>
类型T
未完全定义时,也可能会使用class AbraCadabra;
boost::shared_ptr<AbraCadabra> myPtr;
// ...
。
那是:
shared_ptr
这将编译&amp;工作。与智能指针的许多其他实现不同,智能指针实际上要求完全定义封装类型以便使用它们。这与智能指针在不再被引用时应该知道删除封装对象的事实有关,并且为了删除对象,必须知道它是什么。
这是通过以下技巧实现的:AbraCadabra* pObj = /* get it from somewhere */;
myPtr.reset(pObj);
实际上包含以下内容:
上面的工厂是一个带有单个虚函数的辅助对象,它应该以正确的方式删除你的对象。
当您为共享指针分配值时,实际上会创建此工厂。
即,以下代码
reset
这是分配此工厂的地方。
注意:AbraCadabra
功能实际上是模板功能。它实际上为指定的类型(作为参数传递的对象的类型)创建工厂。
这是您的类型应该完全定义的地方。也就是说,如果它仍未定义 - 您将收到编译错误。
另请注意:如果您实际创建派生类型的对象(派生自shared_ptr
),并将其分配给shared_ptr
- 即使您的析构函数是正确的,它也会以正确的方式删除不是虚拟的。
reset
将始终根据shared_ptr
函数中显示的类型删除对象。
因此shared_ptr是一个非常复杂的智能指针变体。它提供了令人敬畏的灵活性。但是,您应该知道,与智能指针的其他可能实现相比,这种灵活性的性能非常差 。
另一方面 - 有所谓的“侵入式”智能指针。他们没有那么多的灵活性,相反,他们提供了最好的表现。
shared_ptr
的优点与inrusive智能指针相比:
shared_ptr
时定义封装类型。这对大型项目非常有价值,可以大大减少依赖性。 reset
的缺点与inrusive智能指针相比:
shared_ptr
上。当一个shared_ptr
被分配给另一个时 - 不再分配任何内容。使用侵入式智能指针时,您可以自由地将智能指针与原始指针混合使用。这没关系,因为实际引用计数驻留在对象本身内部,这是单个的。 相比之下 - 使用{{1}},你可以不与原始指针混合。
AbraCadabra * pObj = / *从某个地方获取它* /; myPtr.reset(pObj); // ... pObj = myPtr.get(); boost :: shared_ptr myPtr2(pObj); // oops
上述情况将会崩溃。
答案 2 :(得分:11)
至少有三种众所周知的机制。
当创建第一个指向对象的共享指针时,会创建一个单独的引用计数对象并初始化为1.复制指针时,引用计数会增加;当指针被销毁时,它会减少。指针赋值增加一个计数并减少另一个计数(按此顺序,否则自我赋值ptr=ptr
将中断)。如果引用计数为零,则不再存在指针,并删除该对象。
内部计数器要求指向的对象具有计数器字段。这通常通过从特定基类派生来实现。作为交换,这节省了引用计数的堆分配,并且它允许从原始指针重复创建共享指针(使用外部计数器,最终会为一个对象提供两个计数)
您可以将所有共享指针保存在循环图中的对象中,而不是使用计数器。创建的第一个指针指向自身。复制指针时,将副本插入圆圈。删除后,将其从圆圈中删除。但是当被破坏的指针指向自身时,即当它是唯一的指针时,你会删除指向的对象。
缺点是从循环单链表中删除节点相当昂贵,因为您必须迭代所有节点才能找到前一个节点。由于参考地点不佳,这可能会特别痛苦。
可以组合第二和第三个想法:基类可以是该循环图的一部分,而不是包含计数。当然,这意味着只有当对象指向自身时才能删除它(循环长度为1,没有剩余的指针)。同样,优点是你可以从弱指针创建智能指针,但是从链中删除指针的性能不佳仍然是一个问题。
想法3的确切图形结构无关紧要。您还可以创建二叉树结构,在根位置指向对象。同样,硬操作是从该图中删除共享指针节点。好处是如果你在许多线程上有很多指针,那么增长的图形部分并不是一个高度竞争的操作。
答案 3 :(得分:0)
它们包含一个内部引用计数,该计数在shared_ptr复制构造函数/赋值运算符中递增,并在析构函数中递减。当计数达到零时,保留的指针将被删除。
这是智能指针的Boost库documentation。我认为TR1的实现与boost::shared_ptr
大致相同。
答案 4 :(得分:0)
“共享指针是一个智能指针(一个C ++对象,带有重载的运算符*()和运算符 - >;()),它保存指向对象的指针和指向共享引用计数的指针。每次复制一个智能指针使用复制构造函数,引用计数递增。当共享指针被销毁时,其对象的引用计数递减。从原始指针构造的共享指针最初的引用计数为1.当引用计数达到时0,指向的对象被破坏,它占用的内存被释放。你不需要显式地销毁对象:它将在最后一个指针的析构函数运行时自动完成。 来自here。