共享指针如何工作?

时间:2010-05-10 13:32:27

标签: c++ c++11 shared-ptr

共享指针如何知道有多少指针指向该对象? (在这种情况下为shared_ptr)

5 个答案:

答案 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); 实际上包含以下内容:

  1. 指向对象的不透明指针
  2. 共享参考计数器(James McNellis所描述的)
  3. 指向已分配的工厂的指针,该指针知道如何销毁您的对象。
  4. 上面的工厂是一个带有单个虚函数的辅助对象,它应该以正确的方式删除你的对象。

    当您为共享指针分配值时,实际上会创建此工厂。

    即,以下代码

    reset

    这是分配此工厂的地方。 注意:AbraCadabra功能实际上是模板功能。它实际上为指定的类型(作为参数传递的对象的类型)创建工厂。 这是您的类型应该完全定义的地方。也就是说,如果它仍未定义 - 您将收到编译错误。

    另请注意:如果您实际创建派生类型的对象(派生自shared_ptr),并将其分配给shared_ptr - 即使您的析构函数是正确的,它也会以正确的方式删除不是虚拟的。 reset将始终根据shared_ptr函数中显示的类型删除对象。

    因此shared_ptr是一个非常复杂的智能指针变体。它提供了令人敬畏的灵活性。但是,您应该知道,与智能指针的其他可能实现相比,这种灵活性的性能非常差

    另一方面 - 有所谓的“侵入式”智能指针。他们没有那么多的灵活性,相反,他们提供了最好的表现。

    shared_ptr的优点与inrusive智能指针相比:

    • 使用非常灵活。只需在将其分配给shared_ptr时定义封装类型。这对大型项目非常有价值,可以大大减少依赖性。
    • 封装类型不必具有虚拟析构函数,仍然可以正确删除多态类型。
    • 可以与弱指针一起使用。

    reset的缺点与inrusive智能指针相比:

    1. 非常野蛮的性能和浪费堆内存。在赋值时分配另外两个对象:引用计数器,加上工厂(浪费内存,慢)。但这只发生在shared_ptr上。当一个shared_ptr被分配给另一个时 - 不再分配任何内容。
    2. 以上可能会抛出异常。 (内存不足的情况)。相反,intrsusive智能指针可能永远不会抛出(除了与无效内存访问,堆栈溢出等相关的进程异常)。
    3. 删除对象也很慢:需要释放另外两个结构。
    4. 使用侵入式智能指针时,您可以自由地将智能指针与原始指针混合使用。这没关系,因为实际引用计数驻留在对象本身内部,这是单个的。 相比之下 - 使用{{1}},你可以与原始指针混合。

      AbraCadabra * pObj = / *从某个地方获取它* /; myPtr.reset(pObj); // ... pObj = myPtr.get(); boost :: shared_ptr myPtr2(pObj); // oops

    5. 上述情况将会崩溃。

答案 2 :(得分:11)

至少有三种众所周知的机制。

外部计数器

当创建第一个指向对象的共享指针时,会创建一个单独的引用计数对象并初始化为1.复制指针时,引用计数会增加;当指针被销​​毁时,它会减少。指针赋值增加一个计数并减少另一个计数(按此顺序,否则自我赋值ptr=ptr将中断)。如果引用计数为零,则不再存在指针,并删除该对象。

内部计数器

内部计数器要求指向的对象具有计数器字段。这通常通过从特定基类派生来实现。作为交换,这节省了引用计数的堆分配,并且它允许从原始指针重复创建共享指针(使用外部计数器,最终会为一个对象提供两个计数)

循环链接

您可以将所有共享指针保存在循环图中的对象中,而不是使用计数器。创建的第一个指针指向自身。复制指针时,将副本插入圆圈。删除后,将其从圆圈中删除。但是当被破坏的指针指向自身时,即当它是唯一的指针时,你会删除指向的对象。

缺点是从循环单链表中删除节点相当昂贵,因为您必须迭代所有节点才能找到前一个节点。由于参考地点不佳,这可能会特别痛苦。

的变化

可以组合第二和第三个想法:基类可以是该循环图的一部分,而不是包含计数。当然,这意味着只有当对象指向自身时才能删除它(循环长度为1,没有剩余的指针)。同样,优点是你可以从弱指针创建智能指针,但是从链中删除指针的性能不佳仍然是一个问题。

想法3的确切图形结构无关紧要。您还可以创建二叉树结构,在根位置指向对象。同样,硬操作是从该图中删除共享指针节点。好处是如果你在许多线程上有很多指针,那么增长的图形部分并不是一个高度竞争的操作。

答案 3 :(得分:0)

它们包含一个内部引用计数,该计数在shared_ptr复制构造函数/赋值运算符中递增,并在析构函数中递减。当计数达到零时,保留的指针将被删除。

这是智能指针的Boost库documentation。我认为TR1的实现与boost::shared_ptr大致相同。

答案 4 :(得分:0)

“共享指针是一个智能指针(一个C ++对象,带有重载的运算符*()和运算符 - >;()),它保存指向对象的指针和指向共享引用计数的指针。每次复制一个智能指针使用复制构造函数,引用计数递增。当共享指针被销毁时,其对象的引用计数递减。从原始指针构造的共享指针最初的引用计数为1.当引用计数达到时0,指向的对象被破坏,它占用的内存被释放。你不需要显式地销毁对象:它将在最后一个指针的析构函数运行时自动完成。 来自here