Make_shared - 自己的实现 [英] Make_shared - own implementation

查看:76
本文介绍了Make_shared - 自己的实现的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试自己实现 shared_ptr.我在使用 make_shared 时遇到问题.std::make_shared 的主要特点是在连续的内存块中分配计数器块和对象.我怎么能做同样的事情?

I am trying to make my own implementation of shared_ptr. I have problems with make_shared. The main feature of std::make_shared that it allocates counter block and object in continuous block of memory. How I can do the same?

我尝试做这样的事情:

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;
}

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

But when I delete object and counter block, the invalid heap block exception occurs.

推荐答案

N.B._T 是一个保留名称,您不得将其用于您自己的类型/变量的名称/参数等

N.B. _T is a reserved name and you must not use it for names of your own types/variables/parameters etc.

问题出在这里:

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

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

This is wrong for the make_shared case because you didn't allocate two separate objects.

Boost 和 GCC 的 shared_ptrmake_shared 采取的方法是使用一种新的派生类型的控制块,它包括基类中的引用计数并添加存储派生类型中托管对象的空间.如果您让 _ref_cntr 负责通过虚函数删除对象,则派生类型可以覆盖该虚函数以执行不同的操作(例如,仅使用显式析构函数调用来销毁对象而不释放存储空间).

The approach taken for make_shared in Boost's and GCC's shared_ptr is to use a new derived type of control block, which includes the reference counts in the base class and adds storage space for the managed object in the derived type. If you make _ref_cntr responsible for deleting the object via a virtual function then the derived type can override that virtual function to do something different (e.g. just use an explicit destructor call to destroy the object without freeing the storage).

如果你给 _ref_cntr 一个虚拟析构函数,那么 delete _ref_counter 将正确地销毁派生类型,所以它应该变成这样:

If you give _ref_cntr a virtual destructor then delete _ref_counter will correctly destroy the derived type, so it should become something like:

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

虽然如果你不打算添加 weak_ptr 支持,那么就没有必要将托管对象和控制块的析构分开,你可以让控制块的析构函数同时做这两件事:

Although if you don't plan to add weak_ptr support then there is no need to separate the destruction of the managed object and the control block, you can just have the control block's destructor do both:

void _delete_ptr()
{
    delete _ref_counter;
}

您当前的设计不支持 shared_ptr 的一个重要属性,即 template显式 shared_ptr(Y* ptr) 构造函数必须记住 ptr 的原始类型并对其调用 delete,而不是在 _obj_ptr(已转换为 >T*).有关 的相应构造函数,请参阅文档中的 noteboost::shared_ptr.为了实现这一点,_ref_cntr 需要使用类型擦除来存储原始指针,与 shared_ptr 对象中的 _obj_ptr 分开,以便_ref_cntr::dispose() 可以删除正确的值.设计中的这种变化也需要支持 别名构造函数.

Your current design fails to support an important property of shared_ptr, which is that the template<class Y> explicit shared_ptr(Y* ptr) constructor must remember the original type of ptr and call delete on that, not on _obj_ptr (which has been converted to T*). See the note in the docs for the corresponding constructor of boost::shared_ptr. To make that work the _ref_cntr needs to use type-erasure to store the original pointer, separate from the _obj_ptr in the shared_ptr object, so that _ref_cntr::dispose() can delete the correct value. That change in the design is also needed to support the 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 变成:

With this design, make_shared becomes:

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 两个不同的对象.

So _ref_counter points to the allocated control block and when you do delete _ref_counter that means you you have a correctly-matched new/delete pair that allocates and deallocates the same object, instead of creating one object with new then trying to delete two different objects.

要添加 weak_ptr 支持,您需要向控制块添加第二个计数,并将对 dispose() 的调用移出析构函数,因此它被调用当第一个计数变为零时(例如在 dec() 中),并且只有在第二个计数变为零时才调用析构函数.然后以线程安全的方式完成所有这些工作会增加许多微妙的复杂性,这比这个答案需要更长的时间来解释.

To add weak_ptr support you need to add a second count to the control block, and move the call to dispose() out of the destructor, so it is called when the first count goes to zero (e.g. in dec()) and only call the destructor when the second count goes to zero. Then to do all that in a thread-safe way adds a lot of subtle complexity that would take much longer to explain than this answer.

另外,你的这部分实现是错误的,会导致内存泄漏:

Also, this part of your implementation is wrong and leaks memory:

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

可以使用空指针构造一个 shared_ptr,例如shared_ptr((int*)nullptr),在这种情况下,构造函数将分配一个控制块,但由于 _obj_ptr 为空,您永远不会删除控制块.

It's possible to constructor a shared_ptr with a null pointer, e.g. shared_ptr<int>((int*)nullptr), in which case the constructor will allocate a control block, but because _obj_ptr is null you will never delete the control block.

这篇关于Make_shared - 自己的实现的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆