2014-12-09 69 views
4

我努力使自己实现shared_ptr。我遇到问题make_shared。的std::make_shared的主要特征在于它分配计数器块和对象在连续的存储器块。我如何做同样的事情?Make_shared - 自己实现

我试图做这样的事情:

template<class T> 
class shared_ptr 
{ 
private: 
    class _ref_cntr 
    { 
    private: 
     long counter; 

    public: 
     _ref_cntr() : 
      counter(1) 
     { 
     } 

     void inc() 
     { 
      ++counter; 
     } 

     void dec() 
     { 
      if (counter == 0) 
      { 
       throw std::logic_error("already zero"); 
      } 

      --counter; 
     } 

     long use_count() const 
     { 
      return counter; 
     } 
    }; 

    template<class _T> 
    struct _object_and_block 
    { 
     _T object; 
     _ref_cntr cntr_block; 

     template<class ... Args> 
     _object_and_block(Args && ...args) : 
      object(args...) 
     { 
     } 
    }; 

    T* _obj_ptr; 
    _ref_cntr* _ref_counter; 

    void _check_delete_ptr() 
    { 
     if (_obj_ptr == nullptr) 
     { 
      return; 
     } 

     _ref_counter->dec(); 

     if (_ref_counter->use_count() == 0) 
     { 
      _delete_ptr(); 
     } 

     _obj_ptr = nullptr; 
     _ref_counter = nullptr; 
    } 

    void _delete_ptr() 
    { 
     delete _ref_counter; 
     delete _obj_ptr; 
    } 

    template<class _T, class ... Args> 
    friend shared_ptr<_T> make_shared(Args && ... args); 

public: 
    shared_ptr() : 
     _obj_ptr(nullptr), 
     _ref_counter(nullptr) 
    { 
    } 

    template<class _T> 
    explicit shared_ptr(_T* ptr) 
    { 
     _ref_counter = new counter_block(); 
     _obj_ptr = ptr; 
    } 

    template<class _T> 
    shared_ptr(const shared_ptr<_T> & other) 
    { 
     *this = other; 
    } 

    template<class _T> 
    shared_ptr<T> & operator=(const shared_ptr<_T> & other) 
    { 
     _obj_ptr = other._obj_ptr; 
     _ref_counter = other._ref_counter; 

     _ref_counter->inc(); 

     return *this; 
    } 

    ~shared_ptr() 
    { 
     _check_delete_ptr(); 
    } 

}; 

template<class T, class ... Args> 
shared_ptr<T> make_shared(Args && ... args) 
{ 
    shared_ptr<T> ptr; 
    auto tmp_object = new shared_ptr<T>::_object_and_block<T>(args...); 
    ptr._obj_ptr = &tmp_object->object; 
    ptr._ref_counter = &tmp_object->cntr_block; 

    return ptr; 
} 

但是当我删除对象和计数器块,无效的堆块发生异常。

+3

围绕共享指针管理的两个强制性代码片段是(a)引用计数算法,以及(b)实际的* delete *,这两个选项都不包括在本文中。我们不介意读者。发布展示实际问题的[**完整MCVE **](http://stackoverflow.com/help/mcve)。声明“当我删除对象和计数器块...”表明你正在删除两个从未被直接分配的东西(实际上唯一的直接分配tmp_object永远不会被保留)。 – WhozCraig 2014-12-09 11:39:07

+0

@WhozCraig谢谢!我已更新帖子 – 2014-12-09 12:33:19

+2

您的实施缺少一些关键功能。如果添加它们,解决方案可能会简单地显示在现有代码之外。缺少的东西:删除器的类型擦除,存储与托管对象无关的指针(也许更多,比如线程安全的引用计数,弱引用计数,..)'shared_ptr'是复杂的,并且花费很长时间来开发目前的形式。 – dyp 2014-12-09 12:50:01

回答

8

注: _Treserved name,你不能使用它自己的类型/变量/参数等

的名字的问题是在这里:

void _delete_ptr() 
{ 
    delete _ref_counter; 
    delete _obj_ptr; 
} 

这是错误的,make_shared情况,因为你没有分配两个单独的对象。

Boost和GCC的shared_ptr中的make_shared采用的方法是使用新的派生类型的控制块,其中包括基类中的引用计数并为派生类型中的被管理对象添加存储空间。如果你让_ref_cntr负责通过虚拟函数删除对象,那么派生类型可以覆盖该虚函数以做一些不同的事情(例如,只使用显式析构函数调用来销毁对象而不释放存储)。

如果你给_ref_cntr虚析构函数,然后delete _ref_counter将正确地破坏派生类型,因此它应该成为这样的:

void _delete_ptr() 
{ 
    _ref_counter->dispose(); 
    delete _ref_counter; 
} 

但如果你不打算增加weak_ptr支持则没有必要分离的管理对象和控制块的破坏,你可以有控制块的析构函数一举两得:

void _delete_ptr() 
{ 
    delete _ref_counter; 
} 

您当前的设计无法支持导入的shared_ptr蚂蚁属性,这是该template<class Y> explicit shared_ptr(Y* ptr)构造函数必须记住原始类型的ptr,并调用该删除,而不是_obj_ptr(已转换为T*)。看到文档的noteboost::shared_ptr相应的构造函数。为了使这项工作的_ref_cntr需要使用类型擦除存储原始指针,从shared_ptr对象_obj_ptr分开,使_ref_cntr::dispose()可以删除正确的值。设计中的这种变化也需要支持aliasing constructor

class _ref_cntr 
{ 
private: 
    long counter; 

public: 
    _ref_cntr() : 
     counter(1) 
    { 
    } 

    virtual ~_ref_cntr() { dispose(); } 

    void inc() 
    { 
     ++counter; 
    } 

    void dec() 
    { 
     if (counter == 0) 
     { 
      throw std::logic_error("already zero"); 
     } 

     --counter; 
    } 

    long use_count() const 
    { 
     return counter; 
    } 

    virtual void dispose() = 0; 
}; 

template<class Y> 
struct _ptr_and_block : _ref_cntr 
{ 
    Y* _ptr; 
    explicit _ptr_and_block(Y* p) : _ptr(p) { } 
    virtual void dispose() { delete _ptr; } 
}; 

template<class Y> 
struct _object_and_block : _ref_cntr 
{ 
    Y object; 

    template<class ... Args> 
    _object_and_block(Args && ...args) : 
     object(args...) 
    { 
    } 

    virtual void dispose() { /* no-op */ } 
}; 

采用这种设计,make_shared变为:

template<class T, class ... Args> 
shared_ptr<T> make_shared(Args && ... args) 
{ 
    shared_ptr<T> ptr; 
    auto tmp_object = new shared_ptr<T>::_object_and_block<T>(args...); 
    ptr._obj_ptr = &tmp_object->object; 
    ptr._ref_counter = tmp_object; 

    return ptr; 
} 

所以_ref_counter指向分配控制块,当你做delete _ref_counter这意味着你你有一个正确的匹配new/delete一对分配和取消分配同一个对象,而不是用new创建一个对象,然后尝试delete两个不同的对象。

要添加weak_ptr支持,你需要第二次计数添加到控制块,呼叫转移到dispose()出来的析构函数,因此它被称为当第一计数为零(例如,在dec()),只有通话第二个计数变为零时的析构函数。然后以线程安全的方式做所有这些都会增加很多微妙的复杂性,这将比解答需要更长的时间来解释。

而且,您的实现,这部分是错误的,出现内存泄漏:

void _check_delete_ptr() 
{ 
    if (_obj_ptr == nullptr) 
    { 
     return; 
    } 

这是可能的构造函数shared_ptr空指针,例如shared_ptr<int>((int*)nullptr),在这种情况下,构造函数将分配一个控制块,但由于_obj_ptr为空,所以不会删除控制块。

+1

哇!太奇妙了。但我有一个关于weak_ptr支持的问题。当没有'shared_ptr'时,我应该删除_obj_ptr。但是,当仍然有一些'weak_ptr's,我仍然应该维护'_ref_cntr'对象,对吧?但是当这个'_ref_cntr'是'_object_and_block'时,我不能删除_obj_ptr而不删除_ref_cntr。 – 2014-12-09 14:08:12

+1

是的,没错。在GCC的实现中,我的'_object_and_block'的等价物没有'Y'类型的成员,而是它有一个'std :: aligned_storage :: type'类型的原始内存缓冲区,然后它创建'Y'在缓冲区中使用新的位置,'dispose()'''reinterpret_cast (&buffer) - >〜Y()'来销毁它。这允许'Y'对象的生命周期比包含它的'_object_and_block'的生命周期更早结束。 – 2014-12-09 14:25:21

+0

因此,即使在GCC中,只有当最后一个'weak_ptr'死亡时,对象的内存才会释放? – 2014-12-09 14:37:14