N.B. _T
is a 保留名称 https://stackoverflow.com/q/228783/981959并且您不得将其用于您自己的类型/变量/参数等的名称。
问题就在这里:
void _delete_ptr()
{
delete _ref_counter;
delete _obj_ptr;
}
这是错误的make_shared
情况是因为您没有分配两个单独的对象。
采取的方法是make_shared
在 Boost 和 GCC 中shared_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
并在其上调用删除,而不是在_obj_ptr
(已转换为T*
)。请参阅note http://www.boost.org/libs/smart_ptr/shared_ptr.htm#pointer_constructor在相应构造函数的文档中boost::shared_ptr
。为了使这项工作发挥作用_ref_cntr
需要使用类型擦除来存储原始指针,与_obj_ptr
in the shared_ptr
对象,使得_ref_cntr::dispose()
可以删除正确的值。设计上的改变也需要支持别名构造函数 http://www.boost.org/doc/libs/1_57_0/libs/smart_ptr/shared_ptr.htm#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;
}
So _ref_counter
指向分配的控制块,当你这样做时delete _ref_counter
这意味着你有一个正确匹配的new
/delete
分配和释放同一对象的对,而不是使用创建一个对象new
然后尝试delete
两个不同的对象。
To add weak_ptr
支持您需要向控制块添加第二个计数,并将调用移至dispose()
在析构函数之外,因此当第一个计数变为零时(例如,在dec()
)并且仅在第二个计数变为零时调用析构函数。然后,以线程安全的方式完成所有这一切会增加很多微妙的复杂性,这将比这个答案花费更长的时间来解释。
另外,这部分的实现是错误的并且会泄漏内存:
void _check_delete_ptr()
{
if (_obj_ptr == nullptr)
{
return;
}
可以构造一个shared_ptr
带有空指针,例如shared_ptr<int>((int*)nullptr)
,在这种情况下构造函数将分配一个控制块,但是因为_obj_ptr
为 null 你将永远不会删除控制块。