C ++ std :: unique_ptr:为什么lambdas没有任何大小的费用?
我正在阅读“Effective Modern 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
应该有一个types为decltype(deleter_)
的对象,并为该内部对象分配deleter_
。 但显然这不是发生了什么事情。 你可以用最小的代码示例来解释这个机制吗?
unique_ptr
必须始终存储其删除程序。 现在,如果删除器是一个没有状态的types,那么unique_ptr
可以利用空的基本优化 ,以使删除器不使用任何额外的空间。
如何做到这一点是不同的实现。 例如, libc ++和MSVC都将托pipe指针和删除器存储在一个压缩对中 ,如果涉及的types之一是空类,则会自动获得空基优化。
从上面的libc ++链接
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_;
libstdc ++将这两个存储在一个std::tuple
,一些Googlesearchbuild议他们的tuple
实现使用空的基本优化,但是我找不到任何明确指出的文档。
在任何情况下, 这个例子都演示了libc ++和libstdc ++都使用EBO来减less具有空删除器的unique_ptr
的大小。
如果删除者是无状态的,则不需要存储空间。 如果删除器不是无状态的,则状态需要存储在unique_ptr
本身中。
std::function
和函数指针的信息只能在运行时使用,所以必须将其存储在对象旁边的对象本身。 这又需要分配(在unique_ptr
本身中)空间来存储该额外的状态。
也许理解空基优化将帮助你理解如何在实践中实现这一点。
std::is_empty
types特征是另一种可能的实现方式。
图书馆作者究竟如何实现这一点显然取决于他们和标准允许的。
从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
的大小可以与所包含的指针大小相同,即:与普通指针的大小相同。
事实上,对于不是无国籍的lambda而言,会有一个规模的惩罚,也就是说,捕获一个或多个价值的lambda。
但是对于不捕捉lambdas,有两个关键的事实要注意:
- lambda的types是唯一的 ,只有编译器才知道。
- 非捕获的lambda是无状态的。
因此,编译器能够根据其types来纯粹地调用lambda,这被logging为unique_ptr
types的一部分; 不需要额外的运行时信息。
事实上,这就是为什么非捕获的lambda是无状态的。 就大小惩罚问题而言,与其他无状态删除函子types相比,非捕获lambda当然没有什么特别之处。
请注意, std::function
不是无状态的,这就是为什么相同的推理不适用于它。
最后,请注意,虽然无状态对象通常要求非零大小以确保它们具有唯一的地址,但无状态基类不需要添加到派生types的总大小; 这被称为空基优化。 因此, unique_ptr
可以实现(如Bo Perrson的答案),作为从删除types派生的types,如果它是无状态的,将不会贡献大小的惩罚。 (这实际上是正确实现unique_ptr
的唯一方法,对于无状态的删除程序没有大小的惩罚,但我不确定。)