【问题标题】:How to use lambda as std::unique_ptr's Deleter?如何使用 lambda 作为 std::unique_ptr Deleter?
【发布时间】:2019-02-10 02:09:23
【问题描述】:

检查以下人为程序:

#include <functional>
#include <memory>

template<typename T>
using UniPtr = std::unique_ptr<T, std::function<void(T*)>>;

int* alloc()
{
    return new int;
}

UniPtr<int> func()
{
    auto dealloc = [](int* p){delete p;};

    return UniPtr<int>{alloc(), dealloc};
}

int main()
{
    auto p = func();
    return 0;
}

来自std::function constructor manual,我认为构造std::function对象可能会抛出异常,甚至比例很低:

UniPtr<int> func()
{
    auto dealloc = [](int* p){delete p;};

    return UniPtr<int>{alloc(), dealloc};
}

但是如果使用函数指针而不是std::function对象:

template<typename T>
using UniPtr = std::unique_ptr<T, void(*)(T*)>;

我认为离开func() 范围后,dealloc 对象应该被释放,并且它不能被引用。如果我错了,请纠正我。所以我能出来的唯一安全方法是定义一个全局dealloc函数:

void dealloc(int* p)
{
    delete p;
}

但我不喜欢这种方法。

根据先例说明,将lambda 用作std::unique_ptr's Deleter 并不是100% 安全的方式,或者我误解了什么?如何将lambda用作std::unique_ptrDeleter

【问题讨论】:

  • 你的意思是什么根据先例说明,没有100%安全的方式来使用lambda作为std::unique_ptr的Deleter?您指的是链接页面的哪一部分?
  • 我不明白为什么dealloc 需要全局;如果您唯一担心的是它在func() 退出后仍然可以引用,那么您不能只声明它static 吗?
  • @ruakh:你的意思是“static auto dealloc = [](int* p){delete p;};”?是的,我以前无法提出这个想法,谢谢!
  • @Praetorian 对不起,我糟糕的英语让你感到困惑。我的意思是构造 lambdastd::function 对象可能会抛出异常。
  • @NanXiao 是的,确实可以。但大多数(可能是全部)std::function 实现使用小缓冲区优化来避免为小的可调用对象分配动态内存。无捕获的 lambda 几乎肯定会属于这一类。

标签: c++ c++11 lambda smart-pointers unique-ptr


【解决方案1】:

我认为离开func()范围后,dealloc对象应该被释放,不能被引用。

您无需担心。是的,lambda 对象将被销毁,但 lambda's function pointer conversion function 返回的函数指针始终有效,它不会变得悬空。

此转换函数返回的值是一个指向具有 C++ 语言链接的函数的指针,在调用该函数时,与直接调用闭包对象的函数调用运算符具有相同的效果。

【讨论】:

    【解决方案2】:

    如果您将UniPtr 定义为

    template<typename T>
    using UniPtr = std::unique_ptr<T, void(*)(T*)>;
    

    那么下面的代码是有效的,就不用担心删除器的生命周期了

    UniPtr<int> func()
    {
        auto dealloc = [](int* p){delete p;};
        return UniPtr<int>{alloc(), dealloc};
    }
    

    引用 N3337,expr.prim.lambda/6

    没有 lambda-capturelambda-expression 的闭包类型有一个公共的非虚拟非显式 const 转换函数指向具有与闭包类型的函数调用运算符相同的参数和返回类型。 这个转换函数的返回值应该是函数的地址,当被调用时,它与调用闭包类型的函数调用运算符具有相同的效果。

    所以你的删除器是用一个指向函数的指针初始化的,即使你从func返回后它仍然有效。

    【讨论】:

      【解决方案3】:

      在前面的答案中添加一点实现...

      template<typename T, typename D>
      std::unique_ptr<T, D> make_unique_with_deleter(T* t, D d)
      {
          // granted copy elison since C++17
          return std::unique_ptr<T, D> (t, d);
      }
      

      使用:

      class A
      {
      };
      
      auto up1 = make_unique_with_deleter(new A, [](A* a) {delete a; });
      auto up2 = make_unique_with_deleter(std::fopen("something", "w"), std::fclose);
      
      {
          int any_value = 0;
          // caution: only local use with lambda-capture, but possible
          auto up3 = make_unique_with_deleter(new A, [any_value](A* a) {delete a; });
      }
      

      一个快速的解决方案。它适用于不同的场景。它避免了在 std:function 上的使用,其开销很小但不必要。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-07-03
        • 2020-08-19
        • 1970-01-01
        • 2017-04-04
        • 2012-05-04
        • 2021-05-02
        • 1970-01-01
        • 2020-04-17
        相关资源
        最近更新 更多