Make_shared - 自己的实现

时间:2014-12-09 11:28:49

标签: c++ shared-ptr

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

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

1 个答案:

答案 0 :(得分:9)

N.B。 _Treserved name,您不得将其用于您自己的类型/变量/参数等的名称。

问题在于:

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

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

Boost和GCC make_sharedshared_ptr采用的方法是使用新的派生类型的控制块,其中包括基类中的引用计数,并为托管对象添加存储空间。派生类型。如果让_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 1}}(已转换为T*)。请参阅文档中的note,了解boost::shared_ptr的相应构造函数。为了完成这项工作,_ref_cntr需要使用type-erasure来存储原始指针,与_obj_ptr对象中的shared_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为null,所以你永远不会删除控制块。