Make_shared - 自己的实现

Make_shared - own implementation

本文关键字:自己的 实现 shared Make      更新时间:2023-10-16

我正在尝试自己实现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;
}

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

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

问题就在这里:

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

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

在 Boost 和 GCC 的shared_ptrmake_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并在其上调用 delete,而不是在 _obj_ptr(已转换为 T* 上(。有关 boost::shared_ptr 的相应构造函数,请参阅文档中的注释。 要使其正常工作,_ref_cntr需要使用类型擦除来存储原始指针,与shared_ptr对象中的_obj_ptr分开,以便_ref_cntr::dispose()可以删除正确的值。还需要对设计进行这种更改以支持别名构造函数。

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为 null,您将永远不会删除控制块。