为什么我不能将元素移动到不可复制的向量中?
Why can't I move an element into vector of non-copyables?
编译器告诉我我正在尝试访问已删除的函数(即 lambda 表达式的复制构造函数)。但是我看不到哪里。
std::vector<std::function<void()>> tasks;
std::packaged_task<int()> task{ [] { return 1; } };
tasks.emplace_back(
[ t = std::move(task) ] () mutable { t(); });
(我想知道为什么他们在 https://www.slideshare.net/GlobalLogicUkraine/c11-multithreading-futures 中使用 shared_ptr<task>
)。
在 Gcc 和 MSVC 上我得到同样的错误 - 我担心我做错了什么......
error: use of deleted function
'main()::<lambda()>::<lambda>(const main()::<lambda()>&)'
为什么我不能将这个 std::function
放置到矢量上?
简短回答:Lambda 和 std::packaged_task
不是 std::function
。
长答案,你不能将 std::packaged_task
移动到 std::function
以下是我提供的解决方案:
std::vector<std::packaged_task<int()>> tasks;
std::packaged_task<int()> task{ [] () mutable { return 1; } };
tasks.emplace_back( std::move(task) );
如果您确实需要 std::function,而不仅仅是任何可调用对象,则必须将 lambda 绑定到 std::function
来自cppreference:
F must meet the requirements of Callable and CopyConstructible
其中 F
是用于构造 std::function
的函数类型。然而,std::packaged_task
是 not copy constructible。因此在捕获列表中,t
不可复制构造,并且是 lambda 的非静态成员,使得 lambda 的隐式复制构造函数被删除。
std::function
的构造函数要求传递的函数对象是 CopyConstructible
,但 std::packaged_task<F>
不是(对于任何 F
)。 std::function
执行类型擦除动态类型在静态类型中不可见的地方。考虑例如:
int invoke(std::function<int()> f) { return f(); }
int main()
{
std::packaged_task<int()> p{/*etc*/};
auto l = [] { return 5; };
std::function<int()> f( /* either p or l */ );
std::cout << invoke(f) << '\n';
}
对invoke
的调用需要复制f
(按值传递)。但是,f
如果是从l
创建的,则可以复制,但如果是从p
创建的,则不能复制,这与[=17]的静态类型无关=].这个问题基本上有三种方法:
- 禁止在编译时复制
std::function
。
- 允许在编译时复制
std::function
,但如果包含的类型不可复制,则抛出 运行 时间错误。
- 允许在编译时复制
std::function
,并要求您放入其中的任何函数对象都是可复制的。
方法 #1 对函数的存储、传递和共享方式非常严格,并且基本上禁止常见用例,支持使用不可复制函数对象的不常见情况。
方法 #2 是有问题的,因为需要教育用户复制 std::function
在某些情况下可能会失败,并且在编写代码时要非常勤奋。此外,如果设计需要共享函数,则可能需要将它们包装在 std::shared_ptr
中。而如果它们需要被复制并且可能是有状态的,那就更糟了。
无论您如何看待方法 3,它都是标准化的方法。但鉴于上述问题,也很容易防御。
事实上,我已经编写了一个 unique_function
class 模板,它在我当前的项目中使用了方法 #1,因为对于我们来说,存储不可复制的异步任务对象的用例是很常见,没有必要复制或共享这样的任务。
编译器告诉我我正在尝试访问已删除的函数(即 lambda 表达式的复制构造函数)。但是我看不到哪里。
std::vector<std::function<void()>> tasks;
std::packaged_task<int()> task{ [] { return 1; } };
tasks.emplace_back(
[ t = std::move(task) ] () mutable { t(); });
(我想知道为什么他们在 https://www.slideshare.net/GlobalLogicUkraine/c11-multithreading-futures 中使用 shared_ptr<task>
)。
在 Gcc 和 MSVC 上我得到同样的错误 - 我担心我做错了什么......
error: use of deleted function
'main()::<lambda()>::<lambda>(const main()::<lambda()>&)'
为什么我不能将这个 std::function
放置到矢量上?
简短回答:Lambda 和 std::packaged_task
不是 std::function
。
长答案,你不能将 std::packaged_task
移动到 std::function
以下是我提供的解决方案:
std::vector<std::packaged_task<int()>> tasks;
std::packaged_task<int()> task{ [] () mutable { return 1; } };
tasks.emplace_back( std::move(task) );
如果您确实需要 std::function,而不仅仅是任何可调用对象,则必须将 lambda 绑定到 std::function
来自cppreference:
F must meet the requirements of Callable and CopyConstructible
其中 F
是用于构造 std::function
的函数类型。然而,std::packaged_task
是 not copy constructible。因此在捕获列表中,t
不可复制构造,并且是 lambda 的非静态成员,使得 lambda 的隐式复制构造函数被删除。
std::function
的构造函数要求传递的函数对象是 CopyConstructible
,但 std::packaged_task<F>
不是(对于任何 F
)。 std::function
执行类型擦除动态类型在静态类型中不可见的地方。考虑例如:
int invoke(std::function<int()> f) { return f(); }
int main()
{
std::packaged_task<int()> p{/*etc*/};
auto l = [] { return 5; };
std::function<int()> f( /* either p or l */ );
std::cout << invoke(f) << '\n';
}
对invoke
的调用需要复制f
(按值传递)。但是,f
如果是从l
创建的,则可以复制,但如果是从p
创建的,则不能复制,这与[=17]的静态类型无关=].这个问题基本上有三种方法:
- 禁止在编译时复制
std::function
。 - 允许在编译时复制
std::function
,但如果包含的类型不可复制,则抛出 运行 时间错误。 - 允许在编译时复制
std::function
,并要求您放入其中的任何函数对象都是可复制的。
方法 #1 对函数的存储、传递和共享方式非常严格,并且基本上禁止常见用例,支持使用不可复制函数对象的不常见情况。
方法 #2 是有问题的,因为需要教育用户复制 std::function
在某些情况下可能会失败,并且在编写代码时要非常勤奋。此外,如果设计需要共享函数,则可能需要将它们包装在 std::shared_ptr
中。而如果它们需要被复制并且可能是有状态的,那就更糟了。
无论您如何看待方法 3,它都是标准化的方法。但鉴于上述问题,也很容易防御。
事实上,我已经编写了一个 unique_function
class 模板,它在我当前的项目中使用了方法 #1,因为对于我们来说,存储不可复制的异步任务对象的用例是很常见,没有必要复制或共享这样的任务。