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

pur*_*uck 35 c++ tr1 shared-ptr

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

引用计数是如何实现的?它是否使用双向链表?(顺便说一句,我已经用Google搜索了,但我找不到任何可靠的东西.)

使用它有任何陷阱std::tr1::shared_ptr吗?

Emi*_*lia 55

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

所述shared_ptr类通常承载两个成员:一个T*(由返回operator->并在解除引用operator*)和一个aux*其中aux是包含内抽象类:

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

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

  • 指向对象的指针(T*与实际类型相同,但是需要这样才能正确管理作为派生层次结构中具有多个T的基础的所有情况)UT
  • deletor作为删除策略给出的对象的副本到显式构造函数(或者默认deletor只做删除p,上面p是哪里U*)
  • 重写destroy方法,调用deleter functor.

简化的草图可以是这样的:

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; }
};
Run Code Online (Sandbox Code Playgroud)

weak_ptr需要互操作性的情况下,需要第二个计数器(weak_count)aux(将递增/递减weak_ptr),并且delete pa只有当两个计数器都达到零时才必须发生.

  • pa是指向包含计数器和删除器的结构的指针.当强计数变为零时你必须删除该对象,但计数器必须保持直到所有弱点都消失为止:归零的强计数器使得弱者知道它们指向被破坏的对象,弱计数器是仍然需要知道的弱者数量. (6认同)
  • @btshengsheng:是的!是的,它必须是固定的pt。之所以需要“ U”,主要是因为T指向的对象可能不是整个对象,而仅仅是对象的一部分(例如,一个基数),并且适当地删除了对象以获取对象类型的一种方式。是必需的。现在,如果对象是运行时多态的(具有虚拟析构函数),则删除`pt`也会导致调用〜U,但是如果不是,则没有从T *到U *的运行时方法。但是,由于在构造时就知道对象的静态类型,因此我们可以使用多态内部数据来确保U类型的安全。 (2认同)

Zie*_*ezi 29

引用计数是如何实现的?

可以使用基于策略的类设计1将智能指针实现解构为:

  • 存储政策

  • 所有权政策

  • 转换政策

  • 检查政策

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

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

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

在此输入图像描述

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

在此输入图像描述

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

    在此输入图像描述

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

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

在此输入图像描述

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

关于你的第二个问题:

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

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

20.7.2.2.6 shared_ptr creation
...
7. [注意:这些函数通常会分配更多内存,而不是sizeof(T)允许内部簿记结构,例如引用计数. - 尾注]

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

你的第三个问题:

使用它有任何陷阱std::shared_ptr吗?

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

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


1.每个设计功能,如果制定为策略,则具有多个实现.

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

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

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


sth*_*sth 5

如果你想看到所有血淋淋的细节,你可以看看 boost 的shared_ptr实现:

https://github.com/boostorg/smart_ptr

引用计数似乎通常是通过计数器和平台特定的原子递增/递减指令或使用互斥锁进行显式锁定来实现的(请参阅详细命名空间atomic_count_*.hpp中的文件)。