Make_shared - 自己的实现
Make_shared - own implementation
我正在尝试自己实现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_ptr
中make_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,您将永远不会删除控制块。
- 如何使用ZeroMQ为协议缓冲区编写自己的RPC实现
- 在我自己的堆栈中实现top的问题
- 如何实现自己的生成器以与 std 发行版一起使用
- Qt - 重新实现QIODevice,实时听到我自己的声音
- 自己的C++列表类实现(插入函数)出现问题
- 如何自己为我自己的shared_ptr实现实现别名构造函数
- 如果不在派生类实现中执行此操作,"basic_streambuf"是否会创建自己的获取/放置区域?
- 如何将自己的链表实现从存储整数更改为存储个人数据
- 在 gcc 上自己的元组实现段错误,同时在 clang 中工作
- 智能指针的自我/自己的实现
- 在 C++ 中实现自己的字符串类
- count_if使用我自己的类实现第三个参数
- 试图用c++编写我自己的链表实现,在点击列表中的3个元素后编写segfault代码
- 通过我自己的实现从库中覆盖模板方法
- 实现我自己的获取线函数
- 如何在我自己的全系统模拟器上实现 GDB 调试
- 在OpenGL中实现自己的旋转功能
- 在自己的外壳C++中实现历史记录
- Make_shared - 自己的实现
- 库math.h使用fmod和自己的实现