如何实现std :: tr1 :: shared_ptr?

时间:2012-02-08 20:12:09

标签: c++ shared-ptr tr1

我一直在考虑使用共享指针,我知道如何自己实现 - 不想这样做,所以我正在尝试std::tr1::shared_ptr,我有几个问题..

如何实施引用计数?它是否使用双向链表? (顺便说一下,我已经用Google搜索了,但我找不到任何可靠的信息。)

使用std::tr1::shared_ptr是否有任何陷阱?

4 个答案:

答案 0 :(得分:50)

shared_ptr必须管理一个引用计数器和一个删除函数的携带,该函数由初始化时给出的对象类型推导出来。

shared_ptr类通常包含两个成员:T*(由operator->返回并在operator*中取消引用)和aux*其中aux 1}}是一个内部抽象类,包含:

  • 计数器(在复制 - 分配/销毁时递增/递减)
  • 使增量/减量原子化所需的任何内容(如果特定平台原子INC / DEC可用,则不需要)
  • 摘要virtual destroy()=0;
  • 虚拟析构函数。

这样的aux类(实际名称取决于实现)是由一系列模板化类派生的(根据显式构造函数给出的类型进行参数化,比如U派生自{{1} }),添加:

  • 指向对象的指针(与T相同,但与实际类型相同:这需要正确管理T*作为具有多个T的基础的所有情况派生层次结构中的U
  • 作为删除策略提供给显式构造函数的T对象的副本(或默认deletor只执行删除deletor,其中p是{{1}上面)
  • 重写destroy方法,调用删除函数。

简化的草图可以是这个:

p

在需要U*互操作性的情况下,template<class T> class shared_ptr { struct aux { unsigned count; aux() :count(1) {} virtual void destroy()=0; virtual ~aux() {} //must be polymorphic }; template<class U, class Deleter> struct auximpl: public aux { U* p; Deleter d; auximpl(U* pu, Deleter x) :p(pu), d(x) {} virtual void destroy() { d(p); } }; template<class U> struct default_deleter { void operator()(U* p) const { delete p; } }; aux* pa; T* pt; void inc() { if(pa) interlocked_inc(pa->count); } void dec() { if(pa && !interlocked_dec(pa->count)) { pa->destroy(); delete pa; } } public: shared_ptr() :pa(), pt() {} template<class U, class Deleter> shared_ptr(U* pu, Deleter d) :pa(new auximpl<U,Deleter>(pu,d)), pt(pu) {} template<class U> explicit shared_ptr(U* pu) :pa(new auximpl<U,default_deleter<U> >(pu,default_deleter<U>())), pt(pu) {} shared_ptr(const shared_ptr& s) :pa(s.pa), pt(s.pt) { inc(); } template<class U> shared_ptr(const shared_ptr<U>& s) :pa(s.pa), pt(s.pt) { inc(); } ~shared_ptr() { dec(); } shared_ptr& operator=(const shared_ptr& s) { if(this!=&s) { dec(); pa = s.pa; pt=s.pt; inc(); } return *this; } T* operator->() const { return pt; } T& operator*() const { return *pt; } }; 中需要第二个计数器(weak_ptr)(将weak_count增加/减少),aux必须在两个计数器都达到零时发生。

答案 1 :(得分:23)

  

如何实施引用计数?

可以使用policy-based class design 1 将智能指针实现解构为:

  • 存储政策

  • 所有权政策

  • 转化政策

  • 检查政策

包含在模板参数中。 流行的所有权策略包括:深层复制,引用计数,引用链接和破坏性复制。

引用计数跟踪指向(拥有 2 )同一对象的智能指针的数量。当数字变为零时,指针对象被删除 3 。实际的计数器可能是:

  1. 在智能指针对象之间共享,其中每个智能指针都包含指向引用计数器的指针:
  2. enter image description here

    1. 仅包含在一个额外的结构中,该结构为指针对象添加了额外的间接级别。这里,在每个智能指针中保持计数器的空间开销与较慢的访问速度交换:
    2. enter image description here

      1. 包含在指针对象本身内:侵入式引用计数。缺点是对象必须先验地构建,并具有计数功能:

        enter image description here

      2. 最后,您的问题中的方法,使用双向链接列表的引用计数称为引用链接,它是:

      3.   

        ... [1]依赖于观察,你并不真正需要指向一个指针对象的智能指针对象的实际数量;你只需要检测该计数何时下降到零。这导致了保持所有权列表的想法。 :

        enter image description here

          

        引用链接优于引用计数的优点是前者不使用额外的自由存储,这使得它更可靠:创建引用链接的智能指针不会失败。缺点是   该引用链接需要更多的内存用于其簿记(三个指针与仅一个指针加一个整数)。此外,引用计数应该更快一些 - 当您复制智能指针时,只需要间接和增量。列表管理稍微复杂一些。结论,   只有在免费商店稀缺时才应使用引用链接。否则,更喜欢引用计数。

        关于你的第二个问题:

          

        是否(std::shared_ptr)使用双向链表?

        我在C ++标准中找到的所有内容都是:

          

        20.7.2.2.6 shared_ptr创建
          ...
          7. [注意:这些函数通常会分配比sizeof(T)更多的内存,以允许内部簿记结构,例如引用计数。 - 后注]

        在我看来,这不包括双重链表,因为它们不包含实际计数。

        你的第三个问题:

          

        使用std::shared_ptr是否有任何陷阱?

        参考管理计数或链接是资源泄漏的受害者,称为循环引用。让对象A拥有一个指向对象B的智能指针。另外,对象B保存一个指向A的智能指针。这两个对象形成一个循环引用;即使你不再使用它们中的任何一个,它们也互相使用。参考管理策略无法检测此类循环引用,并且这两个对象将永远分配。

        因为shared_ptr的实现使用引用计数,所以循环引用可能是个问题。可以通过更改代码来中断循环shared_ptr链,以使其中一个引用为weak_ptr。这是通过在共享指针和弱指针之间分配值来完成的,但弱指针不会影响引用计数。如果指向对象的唯一指针很弱,则该对象将被销毁。

        1。每个设计功能都有多个实现,如果制定为策略。

        2。智能指针类似于指向使用new分配的对象的指针,不仅指向该对象,还负责其销毁以及释放它占用的内存。

        3。没有其他问题,如果没有使用其他原始指针和/或指向它。

        [1]现代C ++设计:应用通用编程和设计模式。 Andrei Alexandrescu,2001年2月1日

答案 2 :(得分:3)

如果您想查看所有血腥细节,可以查看boost shared_ptr实现:

https://github.com/boostorg/smart_ptr

引用计数似乎通常使用计数器和平台特定的原子递增/递减指令或使用互斥锁显式锁定(请参阅detail namespace中的atomic_count_*.hpp文件)。

答案 3 :(得分:3)

  

使用std::tr1::shared_ptr是否有任何陷阱?

是的,如果在共享内存指针中创建循环,那么当最后一个指针超出范围时,由智能指针管理的内存将不会被回收,因为仍有对指针的引用(即循环原因)引用计数不会降到零。)

例如:

struct A
{
    std::shared_ptr<A> ptr;
};

std::shared_ptr<A> shrd_ptr_1 = std::make_shared(A());
std::shared_ptr<B> shrd_ptr_2 = std::make_shared(A());
shrd_ptr_1->ptr = shrd_ptr_2;
shrd_ptr_2->ptr = shrd_ptr_1;

现在,即使shrd_ptr_1shrd_ptr_2超出范围,它们管理的内存也不会被回收,因为每个ptr成员都指向对方。虽然这是一个非常天真的这种记忆周期的例子,但如果你使用这些类型的指针而没有任何规则,它可以以更加邪恶和难以跟踪的方式出现。例如,我可以看到在哪里尝试实现一个循环链表,其中每个next指针都是std::shared_ptr,如果你不太小心,可能会导致问题。