C++ C++;std::unique#ptr:为什么不';lambdas没有尺码费吗?

C++ C++;std::unique#ptr:为什么不';lambdas没有尺码费吗?,c++,c++11,c++14,unique-ptr,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_); auto

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

假设我们有以下代码:

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

据我所知,
unique\u ptr
应该有一个类型为
decltype(deleter\u)
的对象,并将
deleter\u
分配给该内部对象。但很明显,事实并非如此。您能用尽可能小的代码示例解释一下这背后的机制吗?

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

也许了解这些将有助于您了解如何在实践中实现这一点。
类型特征是如何实现这一点的另一种可能性


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

来自
独特的\u 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;

};
模板
类唯一\u ptr
{
公众:
//公共接口。。。
私人:
//使用空基类优化以节省空间
//使用默认值创建唯一的\u ptr\u删除与指针相同的大小
类_UniquePtrImpl:private deleter_类型
{
公众:
constexpr_UniquePtrImpl()noexcept=默认值;
//其他一些构造器。。。
deleter_type&_deleter()无例外
{return*this;}
常量deleter\u type&\u deleter()常量noexcept
{return*this;}
指针&\u Ptr()无异常
{return_MyPtr;}
常量指针\u Ptr()常量noexcept
{return_MyPtr;}
私人:
指针_MyPtr;
};
_Uniqueptrimplu MyImpl;
};
\u UniquePtrImpl
类包含指针并从
deleter\u类型派生而来


如果deleter恰好是无状态的,则可以优化基类,使其本身不占用字节。然后整个
unique\u ptr
可以与包含的指针大小相同,即:与普通指针大小相同。

A
unique\u ptr
必须始终存储其删除器。现在,如果删除器是一个没有状态的类类型,那么
unique\u ptr
可以使用,这样删除器就不会使用任何额外的空间

具体的实现方式各不相同。例如,和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_;
模板
类_LIBCPP_TYPE_VIS_ONLY unique_ptr
{
公众:
类型定义_Tp元素_类型;
typedef_Dp deleter_type;
typedef typename uu指针u type::type指针;
私人:
__压缩对(ptr);
libstdc++在
std::tuple
中,一些谷歌搜索建议他们的
tuple
实现采用空基优化,但我找不到任何文档如此明确地说明

在任何情况下,演示了libc++和libstdc++都使用EBO来减小带有空删除器的
唯一\u ptr
的大小。

事实上,对于非无状态的lambda,即捕获一个或多个值的lambda,将有一个大小惩罚

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

  • lambda的类型是唯一的,只有编译器知道
  • 非捕获lambda是无状态的
因此,编译器能够完全根据lambda的类型调用lambda,该类型被记录为
unique_ptr
类型的一部分;不需要额外的运行时信息

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

请注意,
std::function
不是无状态的,这就是为什么相同的推理不适用于它


最后,请注意,虽然无状态对象通常需要具有非零大小以确保它们具有唯一的地址,但无状态基类不需要添加到派生类型的总大小中;这称为空基优化。因此可以实现
unique\u ptr
(如Bo Perrson的回答)作为从deleter类型派生的类型,如果它是无状态的,则不会造成大小惩罚。(实际上,这可能是正确实现
unique\u ptr
而不对无状态deleter造成大小惩罚的唯一方法,但我不确定。)

实现可以对其内部数据使用空基类优化,从而使无状态删除对象“消失”通过从中派生一些其他结构。好的,我已经添加了一个示例。据我所知,lambda不是无状态的。但是还要注意,lambda类型是唯一的,因此只要编译器知道用作删除器的lambda的确切类型,如果lambda实际上没有捕获任何内容(这一个没有),从理论上讲,它应该拥有编译时编写删除代码所需的所有信息,而不需要占用运行时空间。不过,我不知道这是否可以保证。@KyleStrand嗯,通过我的谷歌搜索,我发现lambdas不捕获任何内容是无状态的,而其他则是有状态的;您的inf