如何将 unique_ptr 捕获到 lambda 表达式中?

Posted

技术标签:

【中文标题】如何将 unique_ptr 捕获到 lambda 表达式中?【英文标题】:How to capture a unique_ptr into a lambda expression? 【发布时间】:2012-01-04 09:51:05 【问题描述】:

我尝试了以下方法:

std::function<void ()> getAction(std::unique_ptr<MyClass> &&psomething)
    //The caller given ownership of psomething
    return [psomething]() 
        psomething->do_some_thing();
        //psomething is expected to be released after this point
    ;

但它不能编译。有什么想法吗?

更新:

正如建议的那样,需要一些新的语法来明确指定我们需要将所有权转移给 lambda,我现在正在考虑以下语法:

std::function<void ()> getAction(std::unique_ptr<MyClass> psomething)
    //The caller given ownership of psomething
    return [auto psomething=move(psomething)]() 
        psomething->do_some_thing();
        //psomething is expected to be released after this point
    ;

它会是一个好的候选人吗?

更新 1:

我将展示我对movecopy 的实现,如下所示:

template<typename T>
T copy(const T &t) 
    return t;


//process lvalue references
template<typename T>
T move(T &t) 
    return std::move(t);


class A/*...*/;

void test(A &&a);

int main(int, char **)
    A a;
    test(copy(a));    //OK, copied
    test(move(a));    //OK, moved
    test(A());        //OK, temporary object
    test(copy(A()));  //OK, copying temporary object
    //You can disable this behavior by letting copy accepts T &  
    //test(move(A())); You should never move a temporary object
    //It is not good to have a rvalue version of move.
    //test(a); forbidden, you have to say weather you want to copy or move
    //from a lvalue reference.

【问题讨论】:

【参考方案1】:

lambda generalized capture 在 C++14 中解决了这个问题:

// a unique_ptr is move-only
auto u = make_unique<some_type>(some, parameters); 

// move the unique_ptr into the lambda
go.run([u = move(u)]do_something_with(u););

【讨论】:

这种情况下唯一指针什么时候释放? lambda 什么时候做? 现在这个例子应该是go.run([u = move(u)] ...。似乎没有 = 的构造在 2013 年是可以接受的,但标准的最终确定方式不同。 u 在 lambda const 内吗? 这并没有解决我的问题,因为生成的 lambda 无法构造 std::function。 @Vlad> 你的意思是,如果你想让工厂和 lambda 都拥有 unique_ptr 的副本怎么办?这与 unique_ptr 的概念不冲突吗?【参考方案2】:

您不能在 lambda 中永久捕获 unique_ptr。事实上,如果你想在 lambda 中永久捕获任何东西,它必须是可复制;仅仅可移动是不够的。

这可能被认为是 C++11 中的一个缺陷,但您需要一些语法来明确表示您想将 unique_ptr 值移动到 lambda 中。 C++11 规范的措辞非常谨慎,以防止对命名变量进行隐式移动;这就是std::move 存在的原因,这是一件好事

要执行您想要的操作,需要使用std::bind(这将是半卷积,需要binds 的短序列)或只返回一个常规的旧对象。

另外,永远不要将unique_ptr&amp;&amp; 联系起来,除非您实际上正在编写它的移动构造函数。只看价值;用户可以按价值提供它的唯一方法是使用std::move。实际上,除非您正在编写移动构造函数/赋值运算符(或实现转发功能),否则永远不要通过 &amp;&amp; 获取任何东西通常是个好主意。

【讨论】:

太好了!如果您提供bind 序列的一些示例,我想将此标记为正确答案。 我同意将unique_ptr 带到&amp;&amp; 不是一个好主意。但是,总的来说,我认为 take something by &amp;&amp; 的意思是“我希望调用者实际上给我某物的所有权,而不仅仅是为了参考”。 @EarthEngine:您可以通过 value 获取参数。如果他们传递了一个临时值,那么这个临时值将被移动到您的参数值中。如果他们通过了一个非临时的,他们仍然必须使用std::move,这将导致移动发生在你的argument中。你这样做的方式意味着你的函数没有必须拥有它。 My post here 更详细地解释了这一点。 "确实,除非您正在编写移动构造函数/赋值运算符,否则永远不要通过 &amp;&amp; 获取任何内容通常是个好主意。" 或者除非您是实现完美转发... 此答案应根据 c++14 草案的最新更改进行更新。【参考方案3】:

Nicol Bolas 提到的使用std::bind 的“半卷积”解决方案毕竟还不错:

std::function<void ()> getAction(std::unique_ptr<MyClass>&& psomething)

    return std::bind([] (std::unique_ptr<MyClass>& p)  p->do_some_thing(); ,
                     std::move(psomething));

【讨论】:

在 c++11 中无法编译 由于 std::function 需要是可复制的,我看不出这是如何工作的。【参考方案4】:

对我有用的次优解决方案是将unique_ptr 转换为shared_ptr,然后在lambda 中捕获shared_ptr

std::function<void()> getAction(std::unique_ptr<MyClass> psomething)

    //The caller given ownership of psomething
    std::shared_ptr<MyClass> psomethingShared = std::shared_ptr<MyClass>(std::move(psomething));
    return [psomethingShared]()
    
        psomethingShared->do_some_thing();
    ;

【讨论】:

喜欢这个解决方案(用于 C++11)。就像将 unique 更改为 shared 一样简单。【参考方案5】:

我使用了这个非常狡猾的解决方法,其中包括将unique_ptr 粘贴在shared_ptr 中。这是因为我的代码需要 unique_ptr(由于 API 限制),所以我实际上无法将其转换为 shared_ptr(否则我永远无法找回我的 unique_ptr)。

我使用这个可憎的东西的理由是它是为了我的测试代码,我必须在测试函数调用中std::bind unique_ptr

// Put unique_ptr inside a shared_ptr
auto sh = std::make_shared<std::unique_ptr<Type>>(std::move(unique));

std::function<void()> fnTest = std::bind([this, sh, input, output]() 
    // Move unique_ptr back out of shared_ptr
    auto unique = std::move(*sh.get());

    // Make sure unique_ptr is still valid
    assert(unique);

    // Move unique_ptr over to final function while calling it
    this->run_test(std::move(unique), input, output);
);

现在调用fnTest() 将调用run_test(),同时将unique_ptr 传递给它。第二次调用fnTest() 将导致断言失败,因为unique_ptr 在第一次调用期间已经被移动/丢失。

【讨论】:

以上是关于如何将 unique_ptr 捕获到 lambda 表达式中?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 lambda init-capture 对 unique_ptr 不起作用?

在 c++14 lambda 表达式中捕获和移动 unique_ptr

在 lambda 中移动捕获

为啥我不能在 C++14 的 lambda 中移动 std::unique_ptr?

将 packaged_task 对象移动到 lambda 捕获时出错

如何在标准函数 lambda c++ 11 中正确捕获参数包