C ++ std :: unique_ptr:为什么lambda没有任何大小的费用?

时间:2015-10-22 20:01:02

标签: c++ c++11 c++14 unique-ptr

我正在阅读"有效的现代C ++"。在与std::unique_ptr相关的项目中,它声明如果自定义删除器是无状态对象,则不会出现大小费用,但如果它是函数指针或std::function大小费用发生。你能解释一下原因吗?

我们说我们有以下代码:

auto deleter_ = [](int *p) { doSth(p); delete p; };
std::unique_ptr<int, decltype(deleter_)> up(new int, deleter_);

据我了解,unique_ptr应该有decltype(deleter_)类型的对象,并将deleter_分配给该内部对象。但显然不是发生了什么。你能用最小的代码示例来解释这背后的机制吗?

4 个答案:

答案 0 :(得分:35)

unique_ptr必须始终存储其删除者。现在,如果删除器是没有状态的类类型,那么template <class _Tp, class _Dp = default_delete<_Tp> > class _LIBCPP_TYPE_VIS_ONLY unique_ptr { public: typedef _Tp element_type; typedef _Dp deleter_type; typedef typename __pointer_type<_Tp, deleter_type>::type pointer; private: __compressed_pair<pointer, deleter_type> __ptr_; 可以使用instantiates,这样删除器就不会使用任何额外的空间。

如何完成此操作在实现之间有所不同。例如,empty base optimization和MSVC都将托管指针和删除器存储在libc++中,如果涉及的类型之一是空类,它会自动为您提供空基优化。

从上面的libc ++链接

std::tuple
tuple中的

libstdc ++ compressed pair和一些Google搜索建议他们的unique_ptr实施采用空基优化,但我找不到明确说明的任何文档。

在任何情况下,stores the two都表明libc ++和libstdc ++都使用EBO来减少带有空删除符的{{1}}的大小。

答案 1 :(得分:19)

如果删除器是无状态的,则不需要存储空间。如果删除者不是无状态,那么状态需要存储在unique_ptr本身 std::function和函数指针具有仅在运行时可用的信息,因此必须与对象本身的指针一起存储在对象中。这又需要分配(在unique_ptr本身)空间来存储该额外状态。

或许理解Empty Base Optimization将有助于您了解如何在实践中实施 std::is_empty类型特征是如何实现这一特性的另一种可能性。

库编写者如何实现这一点显然取决于他们以及标准允许的内容。

答案 2 :(得分:13)

来自unique_ptr实施:

template<class _ElementT, class _DeleterT = std::default_delete<_ElementT>>
class unique_ptr
{
public:
   // public interface...

private:

  // using empty base class optimization to save space
  // making unique_ptr with default_delete the same size as pointer

  class _UniquePtrImpl : private deleter_type
  {
  public:
     constexpr _UniquePtrImpl() noexcept = default;

     // some other constructors...

     deleter_type& _Deleter() noexcept
     { return *this; }

     const deleter_type& _Deleter() const noexcept
     { return *this; }

     pointer& _Ptr() noexcept
     { return _MyPtr; }

     const pointer _Ptr() const noexcept
     { return _MyPtr; }

  private:
     pointer   _MyPtr;

  };

  _UniquePtrImpl   _MyImpl;

};

_UniquePtrImpl类包含指针,并派生自deleter_type

如果删除器恰好是无状态的,则可以优化基类,使其自身不占用任何字节。然后整个unique_ptr可以与包含指针的大小相同 - 即:与普通指针大小相同。

答案 3 :(得分:5)

实际上对不是无状态的lambdas进行大小惩罚,即捕获一个或多个值的lambda。

但是对于非捕获的lambdas,有两个关键事实需要注意:

  • lambda的类型是唯一,只有编译器知道。
  • 非捕捉羔羊是无国籍的。

因此,编译器能够纯粹基于其类型调用lambda,它被记录为unique_ptr类型的一部分;不需要额外的运行时信息。

这实际上是为什么非捕获的lambdas是无国籍的。就大小惩罚问题而言,与任何其他无状态删除函子类型相比,非捕获lambdas当然没有什么特别之处。

请注意,std::function 无状态,这就是为什么适用于它的原因。

最后,请注意,尽管无状态对象通常需要具有非零大小以确保它们具有唯一地址,但无状态基类需要添加到派生类型的总大小;这称为空基优化。因此unique_ptr可以实现(如在Bo Perrson的答案中)作为从删除类型派生的类型,如果它是无状态的,则不会造成大小惩罚。 (事实上​​,这可能只是 方式正确实现unique_ptr而没有无状态删除器的大小惩罚,但我不确定。)