如果 lambda 使用 std::move() 捕获不可复制的对象,为啥它是不可移动的?
Posted
技术标签:
【中文标题】如果 lambda 使用 std::move() 捕获不可复制的对象,为啥它是不可移动的?【英文标题】:Why is a lambda not-movable if it captures a not-copiable object using std::move()?如果 lambda 使用 std::move() 捕获不可复制的对象,为什么它是不可移动的? 【发布时间】:2018-06-08 18:03:44 【问题描述】:这是我生成错误的示例代码:
#include <functional>
using namespace std;
struct S
S() = default;
S(const S&) = delete;
S(S&&) = default;
S& operator=(const S&) = delete;
S& operator=(S&&) = delete;
;
template <typename F>
void post(F&& func)
function<void()> fforward<F>(func);
int main()
S s;
post([s2 = move(s)] );
在main()
的lambda 内部,我使用std::move()
捕获局部变量s
。在调用 post() 之前,s2
必须已经移动构造成功。
但是,在 post()
内部,f
不能使用对此 lambda 类型的右值引用来构造。
如果我删除,s2 = move(s)
,f
可以用这个右值引用构造。
为什么添加s2 = move(s)
会导致 lambda 不可移动?
这里是link 尝试使用 coliru。
【问题讨论】:
@Lưu Vĩnh Phúc,请告诉我你为什么不喜欢我的问题。 是什么让你觉得我不喜欢它?我只是为语法高亮添加了一个标签 对不起,我误解了修订日志,并认为你给了问题-1。 还有一个事实是 S 有构造函数,它接受 S 实例而不是 S 引用。我必须删除那些才能到达 lambda。 @SornelHaetir,怎么样?可以和S(const S&) = delete
共存吗?它们是哪些?
【参考方案1】:
您的 lambda 不会因为移动捕获而变得不可移动。但它确实变得不可复制,这是一个问题。
std::function
不支持将提供的仿函数移动到自身中,它总是进行复制。因此,不可复制的 lambda(和其他可调用对象)不能与 std::function
一起使用。这个限制的原因是标准要求std::function
是可复制的,如果用不可复制的可调用对象初始化则无法实现。
【讨论】:
我试过std::function<void()> f = []; auto f2 = move(f); f();
和f()
扔std::bad_function_call
。它是否反驳了您的说法“std::function
不支持将提供的函子移动到自身中,它总是复制。”?
@BenjiMizrahi 不。您将 将仿函数移动到 std::function
与 移动 std::function
对象本身混淆了。 前者不是可能,而后者完全没问题(实际上 移动 存储的仿函数)。前者不可能的原因是标准要求std::function
是可复制的,如果它可以从不可复制的函子构造,则无法实现。【参考方案2】:
问题不在于您的 lambda,而在于您的对象不可复制,因为 std::function
要求其对象是可复制的,编译器会抱怨。您几乎应该始终关注rule-of-zero。
一般:
lambda 既可以是可复制的,也可以是可移动的。 如果 lambda 具有不可复制的捕获,它会使 lambda 本身不可复制。这些对象可以包装在一个 smart_pointer 中,但可以在 lambda 捕获中移动(或复制 -shared_ptr
)。
如果没有按值捕获,则闭包类型(lambda)通常可以轻松复制和轻松移动。
如果值对象捕获的所有对象都是可轻松复制且可轻松移动的非 const 类型(例如 C 类类型),则闭包类型将可轻松复制和轻松移动。
否则,如果存在按值捕获,则闭包类型的移动构造函数将复制按值捕获的对象。
如果有 const 对象的按值捕获,则捕获列表中的任何移动都会产生副本。
如果 lambda 本身是 const,它永远不会移动,只会复制,甚至复制到其他 const lambda。
示例:
#include <iostream>
#include <type_traits>
struct S
S()
std::cout << "ctor" << '\n';
~S() noexcept
std::cout << "dtor" << '\n';
S(const S&)
std::cout << "copy ctor\n";
S(S&&) noexcept noexcept
std::cout << "move ctor\n";
S& operator= (const S&)
std::cout << "copy aop\n";
S& operator= (S&&) noexcept
std::cout << "move aop\n";
;
template <typename T>
void getTraits()
std::cout << std::boolalpha
<< "trivially_copy_constructible? "
<< std::is_trivially_copy_constructible_v<T>
<< "\ntrivially_move_constructible? "
<< std::is_trivially_move_constructible_v<T> << '\n' ;
int main()
S s ;
const S cs;
std::cout << "capture by value\n" ;
//auto closure = [s = std::move(s)] ; // S::move construct // 1.
//auto closure = [cs = std::move(cs)] ; // S::copy construct // 2.
//const auto closure = [s = std::move(s)] ; // S::move construct // 3.
const auto closure = [cs = std::move(cs)] ; // S::copy construct // 4.
getTraits<decltype(closure)>();
const auto copy_constructed = std::move(closure);
const auto move_constructed = std::move(closure);
std::cout << "\ncapture by reference\n";
const auto closure = [&s] ;
getTraits<decltype(closure)>();
一次取消注释 1、2、3、4 并检查输出。请记住 std::move
只是将对象转换为右值引用。
【讨论】:
以上是关于如果 lambda 使用 std::move() 捕获不可复制的对象,为啥它是不可移动的?的主要内容,如果未能解决你的问题,请参考以下文章