如果使用 std::move() 捕获不可复制的对象,为什么 lambda 不可移动?
Why is a lambda not-movable if it captures a not-copiable object using std::move()?
这是我生成错误的示例代码:
#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()> f{forward<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试用大肠杆菌
你的 lambda 不会因为移动捕获而变得不可移动。但它确实变得不可复制,这是一个问题。
std::function
不支持将提供的仿函数移动到自身中,它总是进行复制。因此,不可复制的 lambda(和其他可调用对象)不能与 std::function
一起使用。这种限制的原因是标准要求 std::function
是可复制的,如果它是用不可复制的可调用对象初始化的,则无法实现。
问题不在于你的 lambda,而在于你的对象是不可复制的,因为 std::function
要求它的对象是可复制的编译器抱怨。您几乎应该始终遵循 rule-of-zero.
总的来说:
- lambda 既可以复制也可以移动。
- 如果 lambda 具有不可复制的捕获,则会使 lambda 本身不可复制。这些对象可以包装在 smart_pointer 中,但可以在 lambda 捕获中移动(或复制 -
shared_ptr
)。
- 如果没有按值捕获,则闭包类型(lambda)通常是可平凡复制和可平凡移动的。
- 闭包类型将是平凡可复制和平凡可移动的当且仅当-如果值对象捕获的所有内容都是平凡可复制和平凡可移动的非常量类型(例如类 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
只是将对象转换为右值引用。
这是我生成错误的示例代码:
#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()> f{forward<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试用大肠杆菌
你的 lambda 不会因为移动捕获而变得不可移动。但它确实变得不可复制,这是一个问题。
std::function
不支持将提供的仿函数移动到自身中,它总是进行复制。因此,不可复制的 lambda(和其他可调用对象)不能与 std::function
一起使用。这种限制的原因是标准要求 std::function
是可复制的,如果它是用不可复制的可调用对象初始化的,则无法实现。
问题不在于你的 lambda,而在于你的对象是不可复制的,因为 std::function
要求它的对象是可复制的编译器抱怨。您几乎应该始终遵循 rule-of-zero.
总的来说:
- lambda 既可以复制也可以移动。
- 如果 lambda 具有不可复制的捕获,则会使 lambda 本身不可复制。这些对象可以包装在 smart_pointer 中,但可以在 lambda 捕获中移动(或复制 -
shared_ptr
)。 - 如果没有按值捕获,则闭包类型(lambda)通常是可平凡复制和可平凡移动的。
- 闭包类型将是平凡可复制和平凡可移动的当且仅当-如果值对象捕获的所有内容都是平凡可复制和平凡可移动的非常量类型(例如类 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
只是将对象转换为右值引用。