Make_shared - 自己的实现 [英] Make_shared - own implementation
问题描述
我正在尝试自己实现 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_ptr
中 make_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
构造函数必须记住 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
,在这种情况下,构造函数将分配一个控制块,但由于 _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屋!