std :: unique_ptr和std :: shared_ptr之间的破坏行为差异的基本原理是什么?

时间:2015-02-19 20:20:37

标签: c++ c++14

来自http://en.cppreference.com/w/cpp/memory/unique_ptr

  

如果T是某个基础B的派生类( sic ),那么std::unique_ptr<T>是   隐式可转换为std::unique_ptr<B>。默认删除器   生成的std::unique_ptr<B>将使用B的运算符删除,   导致未定义的行为,除非B的析构函数是虚拟的。   请注意std::shared_ptr行为不同:std::shared_ptr<B>会   对类型T使用operator delete,所拥有的对象将是   即使B的析构函数不是虚拟的,也会正确删除。

上述破坏行为差异的理由是什么?我最初的猜测是性能?

同样有趣的是,如果std::shared_ptr<B>上的析构函数是非虚拟的并且无法远程调用,T如何能够调用类型B的析构函数正如我从std::shared_ptr<B>的背景中看到的那样?

1 个答案:

答案 0 :(得分:8)

std::shared_ptr<X>已经在原始B*上有一堆开销。

shared_ptr<X>基本上维持了4件事。它维护指向B的指针,它保留两个引用计数(&#34;硬&#34;引用计数,&#34;软&#34;一个weak_ptr),它保持清理功能。

清理功能是shared_ptr<X>表现不同的原因。创建shared_ptr<X>时,会创建一个调用该特定类型析构函数的函数,并将其存储在shared_ptr<X>管理的清理函数中。

当您更改托管类型(B*变为C*)时,清理功能保持不变。

因为shared_ptr<X>需要管理引用计数,所以清理函数存储的额外开销是微不足道的。

对于unique_ptr<B>,该类几乎与原始B*一样便宜。它保持除B*以外的零状态,其行为(在破坏时)归结为if (b) delete b;。 (是的,if (b)是多余的,但优化器可以解决这个问题。

为了支持cast-to-base和delete-as-derived,必须存储额外状态,以便记住unique_ptr 真正到派生类。这可以是存储的指向删除指针的形式,如shared_ptr

然而,这会使unique_ptr<B>的大小增加一倍,或者要求它在某处将数据存储在堆上。

决定unique_ptr<B>应该是零开销,因此它仍然不支持cast-to-base同时仍然调用base的析构函数。

现在,你可以通过简单地添加一个删除器类型并存储一个知道它正在销毁的东西类型的销毁函数来教导unique_ptr<B>这样做。上面一直在谈论unique_ptr的默认删除,这是无状态和无关紧要的。

struct deleter {
  void* state;
  void(*f)(void*);
  void operator()(void*)const{if (f) f(state);}
  deleter(deleter const&)=default;
  deleter(deleter&&o):deleter(o) { o.state = nullptr; o.f=nullptr; }
  deleter()=delete;
  template<class T>
  deleter(T*t):
    state(t),
    f([](void*p){delete static_cast<T*>(p);})
  {}
};
template<class T>
using smart_unique_ptr = std::unique_ptr<T, deleter>;

template<class T, class...Args>
smart_unique_ptr<T> make_smart_unique( Args&&... args ) {
  T* t = new T(std::forward<Args>(args)...);
  return { t, t };
}

live example,我生成一个unique-ptr派生,将它存储在unique-ptr to base中,然后重置base。派生指针将被删除。

(一个简单的void(*)(void*)删除器可能遇到问题,传递的void*在基础和派生案例之间的值会有所不同。)

请注意,更改存储在此类unique_ptr中的指针而不更改删除操作将导致不正常的行为。