为什么我不能在C++14的lambda表达式中移动std::unique_ptr?

24

我想在lambda内传递一个原生指针,但如果lambda未被调用,我不希望它泄漏。代码如下:

void Clean(std::unique_ptr<int>&& list);

void f(int* list) {
  thread_pool.Push([list = std::unique_ptr<int>(list) ] {
    Clean(std::move(list));  // <-- here is an error.
  });
}

我在 Clang 3.7.0 中遇到了一个错误:

错误: 将引用绑定到类型为 'unique_ptr<[2 * ...]>' 的值,该值的类型为 'unique_ptr<[2 * ...]>' 会丢失限定符

但我一开始看不到任何限定符,尤其是被丢弃的。

另外,我在邮件列表中发现了类似的报告,但没有回答。


我应该如何修改我的代码,以便它能被编译并按语义正常工作?


什么是 Clean()?当我在 clang 上尝试重现时,假设 Clean() 通过值传递一个 unique_ptr,我得到了“error: call to deleted constructor of std::unique_ptr<int>”的错误提示,这更有意义。 - Barry
@Barry 我没有预料到原来的错误之后还会有其他错误。现在我正在尝试修复所有其他错误并更新问题中的代码,以便原始错误是唯一的。 - abyss.7
1个回答

34

你需要将内部的lambda函数声明为mutable

[this](Pointer* list) {
  thread_pool.Push([this, list = std::unique_ptr<int>(list) ]() mutable {
                                                               ^^^^^^^^^
    Clean(std::move(list));
  });
};

operator() 在 lambda 函数中默认为 const,因此在该调用中无法修改其成员。因此,内部的 list 表现得就像是一个 const std::unique_ptr<int>。当您进行 move 转换时,它会被转换为 const std::unique_ptr<int>&&。这就是为什么您会收到有关放弃限定符的编译错误的原因:您正在尝试将一个 const 右值引用转换为非 const 右值引用。错误可能不够有帮助性,但归根结底,问题在于:您不能 move 一个 const unique_ptr

mutable 可以解决这个问题 - operator() 不再是 const 的,因此该问题不再适用。

注意:如果您的 Clean() 接受的是 unique_ptr<int> 而不是 unique_ptr<int>&&,这更有意义(因为它是一个更明确、确定的 sink),则错误将更加明显:

error: call to deleted constructor of `std::unique_ptr<int>`
note: 'unique_ptr' has been explicitly marked deleted here  

    unique_ptr(const unique_ptr&) = delete
    ^

提及一下,lambda 本身成为了一个只能移动的对象,因此我需要在 thread_pool.Push() 中显式地移动它。 - abyss.7
@Abyss.7 没有必要显式移动lambda:除非您将其存储在某个地方,否则它是一个rvalue。 - Yakk - Adam Nevraumont
@Yakk,是的,我已经意识到了。另外我发现这样的lambda不能存储在std::function中——至少在libc++和libstdc++中不行。所以,现在整个想法对我来说似乎毫无意义,除非我实现自己的移动唯一函子对象。 - abyss.7
1
@abyss.7 是的,std::function 需要复制,调用 <Sig> 和销毁。然而,如果你正在进行线程处理,packaged_task<R> 是移动操作,并为您生成 futures。 - Yakk - Adam Nevraumont

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接