std::shared_ptr 的控制块中的虚函数
virtual function in std::shared_ptr's control block
shared::ptr
我发现的实现是这样写的
namespace detail {
struct deleter_base {
virtual ~deleter_base() {}
virtual void operator()( void* ) = 0;
};
template <typename T>
struct deleter : deleter_base {
virtual void operator()( void* p ) {
delete static_cast<T*>(p);
}
};
}
template <typename T>
class simple_ptr {
T* ptr;
detail::deleter_base* deleter;
public:
template <typename U>
simple_ptr( U* p ) {
ptr = p;
deleter = new detail::deleter<U>();
}
~simple_ptr() {
(*deleter)( ptr );
delete deleter;
}
};
我的问题是
1) 为什么我们需要这样的结构(我的意思是类似于类型擦除技术),我们不能将其作为删除器(见下面的代码)吗?在这里有一个虚函数的目的是什么,据我所知,如果它不是虚拟的,它无论如何都会在正确的类型上调用 delete
(即在 Bar
上键入 std::shared_ptr<Foo>(new Bar)
),因为 simple_ptr
有模板化的构造函数。
template <typename T>
struct deleter {
void operator()( void* p ) {
delete static_cast<T*>(p);
}
};
2) 为什么我们在基class 中需要一个虚拟析构函数?仅仅是因为它包含一个虚函数还是我们的删除器也必须有虚析构函数?
共享指针类型擦除破坏,因为它们已经必须处理引用计数。一直打到擦除破坏并没有那么贵。
如果您希望能够将指向 T 的共享指针转换为指向 void 的共享指针,则需要类型擦除销毁,这有时很有用。一般来说,它不需要你存储的数据有一个虚拟析构函数。
此外,它允许别名共享指针指向在共享指针拥有的资源中。
你的删除器显然不起作用;因为它缺少一个公共基础 class,它只能通常存储在 void*
中,并且您不能用 (ptr)
.
调用 void*
忽略你的shared::ptr
,如果我们看一个工业质量std::shared_ptr
,我们发现引用控制块在其末尾存储类型擦除的删除器,如果你make_shared
它用对象的实例替换类型擦除的删除器。
struct rc_block {
std::atomic<std::size_t> strong;
std::atomic<std::size_t> weak;
virtual void cleanup() = 0;
virtual ~rc_block() {}
};
template<class T>
struct maked_rc_block final {
std::atomic<std::size_t> strong = 1;
std::atomic<std::size_t> weak = 0;
std::aligned_storage<sizeof(T), alignof(T)> t;
template<class... Args>
maked_rc_block(Args&&...args) {
::new( (void*)&t ) T(std::forward<Args>(args)...);
}
void cleanup() override {
((T*)&t)->~T();
}
};
template<class F>
struct action_rc_block final {
std::atomic<std::size_t> strong = 1;
std::atomic<std::size_t> weak = 0;
F f;
void cleanup() { f(); }
template<class IN>
actoin_rc_block(IN&& in):f(std::forward<IN>(in)) {}
};
template<class F>
action_rc_block(F)->action_rc_block<F>;
template<class T>
struct simple_shared {
T* ptr = 0;
rc_block* counters = 0;
simple_shared( simple_shared const& o ):
ptr(o.ptr), counters(o.counters)
{ if (counters) ++(counters->strong); }
~simple_shared() {
if (counters && --(counters->strong)) {
delete counters;
}
}
template<class U>
simple_shared(U* in):
ptr(in),
counters( new action_rc_block{[in]{ delete in; }} )
{}
// explicit deleter
template<class U, class D>
simple_shared(U* in, D&& d):
ptr(in),
counters( new action_rc_block{[in,d=std::forward<D>(d)]{ d(in); }} )
{}
template<class U, class V>
simple_shared(simple_shared<U> const& alias_this, V* v):
ptr(v),
counters(alias_this.counters)
{
if(counters) ++(counters->strong);
}
template<class U>
simple_shared( maked_rc_block<U>* c ):
ptr( c?(T*)&c.t:nullptr ),
counters(c)
{}
};
template<class T, class...Args>
simple_shared<T> make_simple_shared( Args&&... args ) {
auto* counter = new make_rc_block<T>( std::forward<Args>(args)... );
return {counter};
}
我对原子的使用快速而松散,但我希望你能理解。
拥有智能指针的设计space种类繁多;但许多设计选择意味着强约束。
对于任何共享拥有的智能指针,将 lambda 存储到 delete static_cast<T*>(p);
(或 "type erasure")的唯一替代方法是对组中最后一个拥有的智能指针使用删除。
如果您根本不允许转换,并且所有所有者都具有完全相同的指针值,则可以这样做。这样,最后一个实例的销毁保证始终产生相同的效果。
如果您甚至允许派生到基类的转换,这意味着只有当托管对象具有虚拟析构函数时,结果才会被明确定义。您可以在指针值上支持 static_cast
和 dynamic_cast
。
这意味着您不能拥有不属于智能指针类型的自定义删除器对象。
您不能构造一个拥有的智能指针,它可以将控制块分配为对象的一部分(如 make_shared
),除非智能指针具有不同的类型。
您没有通用化 "aliasing constructor",因此您无法获得指向任意子对象或原始对象拥有的对象的拥有智能指针。 (因此,如果您有一个指向集合的智能指针,则无法创建一个指向集合元素的拥有智能指针,以在需要该元素时使集合保持活动状态。)
沿着这条路走下去(避免抽象的成本 delete p;
)将使控制块更小并且删除速度稍微快一些,但它也可能 导致专门拥有的激增不同不兼容类型的智能指针,因为实际中经常需要其他操作
shared::ptr
我发现的实现是这样写的
namespace detail {
struct deleter_base {
virtual ~deleter_base() {}
virtual void operator()( void* ) = 0;
};
template <typename T>
struct deleter : deleter_base {
virtual void operator()( void* p ) {
delete static_cast<T*>(p);
}
};
}
template <typename T>
class simple_ptr {
T* ptr;
detail::deleter_base* deleter;
public:
template <typename U>
simple_ptr( U* p ) {
ptr = p;
deleter = new detail::deleter<U>();
}
~simple_ptr() {
(*deleter)( ptr );
delete deleter;
}
};
我的问题是
1) 为什么我们需要这样的结构(我的意思是类似于类型擦除技术),我们不能将其作为删除器(见下面的代码)吗?在这里有一个虚函数的目的是什么,据我所知,如果它不是虚拟的,它无论如何都会在正确的类型上调用 delete
(即在 Bar
上键入 std::shared_ptr<Foo>(new Bar)
),因为 simple_ptr
有模板化的构造函数。
template <typename T>
struct deleter {
void operator()( void* p ) {
delete static_cast<T*>(p);
}
};
2) 为什么我们在基class 中需要一个虚拟析构函数?仅仅是因为它包含一个虚函数还是我们的删除器也必须有虚析构函数?
共享指针类型擦除破坏,因为它们已经必须处理引用计数。一直打到擦除破坏并没有那么贵。
如果您希望能够将指向 T 的共享指针转换为指向 void 的共享指针,则需要类型擦除销毁,这有时很有用。一般来说,它不需要你存储的数据有一个虚拟析构函数。
此外,它允许别名共享指针指向在共享指针拥有的资源中。
你的删除器显然不起作用;因为它缺少一个公共基础 class,它只能通常存储在 void*
中,并且您不能用 (ptr)
.
void*
忽略你的shared::ptr
,如果我们看一个工业质量std::shared_ptr
,我们发现引用控制块在其末尾存储类型擦除的删除器,如果你make_shared
它用对象的实例替换类型擦除的删除器。
struct rc_block {
std::atomic<std::size_t> strong;
std::atomic<std::size_t> weak;
virtual void cleanup() = 0;
virtual ~rc_block() {}
};
template<class T>
struct maked_rc_block final {
std::atomic<std::size_t> strong = 1;
std::atomic<std::size_t> weak = 0;
std::aligned_storage<sizeof(T), alignof(T)> t;
template<class... Args>
maked_rc_block(Args&&...args) {
::new( (void*)&t ) T(std::forward<Args>(args)...);
}
void cleanup() override {
((T*)&t)->~T();
}
};
template<class F>
struct action_rc_block final {
std::atomic<std::size_t> strong = 1;
std::atomic<std::size_t> weak = 0;
F f;
void cleanup() { f(); }
template<class IN>
actoin_rc_block(IN&& in):f(std::forward<IN>(in)) {}
};
template<class F>
action_rc_block(F)->action_rc_block<F>;
template<class T>
struct simple_shared {
T* ptr = 0;
rc_block* counters = 0;
simple_shared( simple_shared const& o ):
ptr(o.ptr), counters(o.counters)
{ if (counters) ++(counters->strong); }
~simple_shared() {
if (counters && --(counters->strong)) {
delete counters;
}
}
template<class U>
simple_shared(U* in):
ptr(in),
counters( new action_rc_block{[in]{ delete in; }} )
{}
// explicit deleter
template<class U, class D>
simple_shared(U* in, D&& d):
ptr(in),
counters( new action_rc_block{[in,d=std::forward<D>(d)]{ d(in); }} )
{}
template<class U, class V>
simple_shared(simple_shared<U> const& alias_this, V* v):
ptr(v),
counters(alias_this.counters)
{
if(counters) ++(counters->strong);
}
template<class U>
simple_shared( maked_rc_block<U>* c ):
ptr( c?(T*)&c.t:nullptr ),
counters(c)
{}
};
template<class T, class...Args>
simple_shared<T> make_simple_shared( Args&&... args ) {
auto* counter = new make_rc_block<T>( std::forward<Args>(args)... );
return {counter};
}
我对原子的使用快速而松散,但我希望你能理解。
拥有智能指针的设计space种类繁多;但许多设计选择意味着强约束。
对于任何共享拥有的智能指针,将 lambda 存储到 delete static_cast<T*>(p);
(或 "type erasure")的唯一替代方法是对组中最后一个拥有的智能指针使用删除。
如果您根本不允许转换,并且所有所有者都具有完全相同的指针值,则可以这样做。这样,最后一个实例的销毁保证始终产生相同的效果。
如果您甚至允许派生到基类的转换,这意味着只有当托管对象具有虚拟析构函数时,结果才会被明确定义。您可以在指针值上支持 static_cast
和 dynamic_cast
。
这意味着您不能拥有不属于智能指针类型的自定义删除器对象。
您不能构造一个拥有的智能指针,它可以将控制块分配为对象的一部分(如 make_shared
),除非智能指针具有不同的类型。
您没有通用化 "aliasing constructor",因此您无法获得指向任意子对象或原始对象拥有的对象的拥有智能指针。 (因此,如果您有一个指向集合的智能指针,则无法创建一个指向集合元素的拥有智能指针,以在需要该元素时使集合保持活动状态。)
沿着这条路走下去(避免抽象的成本 delete p;
)将使控制块更小并且删除速度稍微快一些,但它也可能 导致专门拥有的激增不同不兼容类型的智能指针,因为实际中经常需要其他操作