【问题标题】:Make_shared - own implementationMake_shared - 自己的实现
【发布时间】:2014-12-09 21:50:23
【问题描述】:

我正在尝试自己实现shared_ptr。我对make_shared 有疑问。 std::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;
}

但是当我删除对象和计数器块时,出现无效堆块异常。

【问题讨论】:

  • 围绕共享指针管理的两个强制性代码是 (a) 引用计数算法,和 (b) 实际的 delete,两者都是您选择的 不包含在这篇文章中。我们不是介意读者。发布显示实际问题的complete MCVE。语句“当我删除对象和计数器块时...”表明您正在删除从未 直接 分配的 两个 事物(实际上是唯一的直接分配,@987654326 @, 根本不会保留)。
  • @WhozCraig 谢谢!我已经更新了帖子
  • 您的实现缺少一些关键功能。如果添加它们,解决方案可能会简单地出现在现有代码之外。缺少什么:删除器的类型擦除,存储与托管对象无关的指针(可能更多,如线程安全引用计数,弱引用计数,..)shared_ptr 很复杂,开发当前形式。

标签: c++ shared-ptr


【解决方案1】:

注意_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&lt;class Y&gt; explicit shared_ptr(Y* ptr) 构造函数必须记住ptr 的原始类型并对其调用delete,而不是_obj_ptr(已转换到T*)。请参阅文档中的note 以获取boost::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() 的调用移出析构函数,因此当第一个计数变为零时调用它(例如在@987654356 @) 并且仅在第二个计数变为零时调用析构函数。然后以线程安全的方式完成所有这些会增加许多微妙的复杂性,这比这个答案需要更长的时间来解释。

另外,您的这部分实现是错误的并且会泄漏内存:

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

可以使用空指针构造shared_ptr,例如shared_ptr&lt;int&gt;((int*)nullptr),在这种情况下构造函数会分配一个控制块,但是因为_obj_ptr 为空,所以你永远不会删除控制块。

【讨论】:

  • 哇!太奇妙了。但我有一个关于weak_ptr 支持的问题。当没有shared_ptrs 时,我应该删除_obj_ptr。但是当还有一些weak_ptrs 时,我还是应该维护_ref_cntr 对象,对吧?但是当这个_ref_cntr_object_and_block时,我不能不删除_ref_cntr就删除_obj_ptr。
  • 是的,没错。在 GCC 的实现中,我的等效 _object_and_block 没有 Y 类型的成员,而是有一个 std::aligned_storage&lt;Y&gt;::type 类型的原始内存缓冲区,然后它使用新位置在该缓冲区中创建 Y,和dispose()reinterpret_cast&lt;Y*&gt;(&amp;buffer)-&gt;~Y() 摧毁它。这允许Y 对象的生命周期比包含它的_object_and_block 的生命周期结束得更早。
  • 那么,即使在 GCC 中,对象的内存也只有在最后一个 weak_ptr 死亡时才会释放?
  • 是的,我知道的所有make_shared 实现都是如此。
  • _object_and_block 仅在调用 make_shared 时使用,并且不支持自定义删除器。对于自定义删除器,您需要另一个派生类型,例如 _ptr_and_deleter_and_block&lt;Y, D&gt; 存储 Y*D(对于自定义分配器类似)。
猜你喜欢
  • 2017-02-22
  • 2016-05-02
  • 2012-02-12
  • 2017-09-19
  • 2015-02-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多