jnb*_*mez 41 c++ unique-ptr c++11 c++14
我正在阅读"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_);
Run Code Online (Sandbox Code Playgroud)
根据我的理解,unique_ptr
应该有一个类型的对象decltype(deleter_)
并分配deleter_
给该内部对象.但显然这不是正在发生的事情.你能用最小的代码示例来解释这背后的机制吗?
Pra*_*ian 37
一个unique_ptr
必须存储其删除器.现在,如果删除器是没有状态的类类型,则unique_ptr
可以使用空基优化,以便删除器不使用任何额外空间.
如何完成这一点在实现之间有所不同.例如,libc ++和MSVC都将托管指针和删除器存储在压缩对中,如果涉及的类型之一是空类,它会自动为您提供空基优化.
从上面的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_;
Run Code Online (Sandbox Code Playgroud)
的libstdc ++ 存储这两种在std::tuple
和一些谷歌的搜索表明其tuple
实现采用空基地优化,但我无法找到任何文件,述明如此明确.
在任何情况下,此示例都演示了libc ++和libstdc ++都使用EBO来减小unique_ptr
带有空删除器的a的大小.
Sir*_*Guy 19
如果删除器是无状态的,则不需要存储空间.如果删除器不是无状态的,那么状态需要存储在unique_ptr
自身中.
std::function
和函数指针具有仅在运行时可用的信息,因此必须与对象本身的指针一起存储在对象中.这又需要分配(在其unique_ptr
自身中)空间来存储该额外状态.
也许了解空基优化将帮助您了解如何在实践中实现这一点.
该std::is_empty
型特点是如何实现这一点的另一种可能性.
图书馆作者如何实现这一点显然取决于他们以及标准允许的内容.
Bo *_*son 15
从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;
};
Run Code Online (Sandbox Code Playgroud)
本_UniquePtrImpl
类包含指针,从派生deleter_type
.
如果删除器恰好是无状态的,则可以优化基类,使其自身不占用任何字节.然后整个unique_ptr
可以与包含的指针大小相同 - 即:与普通指针相同的大小.
事实上,有将是一个大小惩罚lambda表达式是不是无状态的,即lambda表达式捕获一个或多个值.
但对于非捕获lambda,有两个关键事实要注意:
因此,编译器能够纯粹根据其类型调用lambda ,它被记录为类型的一部分unique_ptr
; 不需要额外的运行时信息.
这实际上是为什么非捕获lambda是无国籍的.就大小惩罚问题而言,与任何其他无状态删除函子类型相比,非捕获lambdas当然没有什么特别之处.
请注意,std::function
是不是无状态的,这就是为什么同样的道理也并不适用于它.
最后,要注意的是,尽管无状态的对象通常需要具有非零大小以确保它们具有唯一的地址,无国籍基类不需要添加到派生类型的总大小; 这称为空基优化.因此unique_ptr
可以实现(如在Bo Perrson的答案中)作为从删除类型派生的类型,如果它是无状态的,则不会造成大小惩罚.(事实上,这可能是正确实施而没有无状态删除者的大小惩罚的唯一方法unique_ptr
,但我不确定.)