std :: shared_ptr的控制块中的虚函数

时间:2018-10-23 18:40:08

标签: c++ templates shared-ptr virtual-functions

我发现的

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)为什么在基类中需要虚拟析构函数?仅仅是因为它包含虚拟函数,还是我们的删除程序也必须具有虚拟析构函数?

2 个答案:

答案 0 :(得分:2)

共享指针键入擦除破坏,因为它们已经必须处理引用计数。一路输入擦除破坏并没有那么昂贵。

如果希望将共享指针T转换为void共享指针,则需要

类型擦除销毁,这有时很有用。通常,它消除了您存储的数据具有虚拟析构函数的需求。

此外,它允许别名化的共享指针指向 内共享指针拥有的资源。

您的删除器显然不起作用;由于缺少通用基类,因此只能将其存储在void*中,并且不能使用void*调用(ptr)

忽略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};
}

我对原子的使用是快速而随意的,但我希望你能理解。

答案 1 :(得分:-1)

拥有智能指针的设计空间很大且种类繁多;但是许多设计选择都暗示着强大的约束条件。

对于任何共享的拥有智能指针,将lambda do存储到delete static_cast<T*>(p);(或“类​​型擦除”)的唯一替代方法是对组中最后一个拥有智能指针使用delete。

如果根本不进行任何转换,并且所有所有者的指针值完全相同,则可以这样做。这样一来,确保销毁最后一个实例始终会产生相同的效果。

如果甚至允许从派生到基本的转换,则意味着只有在托管对象具有虚拟析构函数的情况下,结果才能得到很好的定义。您可以在指针值上支持static_castdynamic_cast

这意味着您不能拥有不属于智能指针类型的自定义删除器对象。

除非智能指针具有不同的类型,否则您不能构造自己的智能指针,该智能指针可以将控制块分配为对象的一部分(作为make_shared)。

您没有通用的“别名构造函数”,因此您无法获得指向任意子对象或原始对象拥有的对象的智能指针。 (因此,如果您有一个智能的指向集合的指针,则无法使自己拥有指向该集合的元素的智能指针,以使该集合在需要该元素时仍处于活动状态。)

走这条路(避免抽象delete p;的代价)将使控制块更小,删除的速度稍快一些,但这也可能会导致拥有各种不同的专门拥有的智能指针不兼容的类型,因为实际上经常需要执行其他操作。