应改用 std::move_only_function:因 std::function 要求 CopyConstructible,而捕获 non-copyable 类型的 lambda 拷贝构造被删;std::move_only_function 仅需 MoveConstructible,零开销且绕过拷贝检查。

遇到 error: use of deleted function 'X::X(const X&)' 就该换 std::move_only_function,不是代码写错了,是语义不匹配——std::function 强制要求拷贝,而你手里的 lambda 根本不允许被拷贝。
为什么 std::function 编译失败而 std::move_only_function 可以
根本原因在类型约束:std::function 要求目标可调用对象满足 CopyConstructible;但只要 lambda 捕获了 std::unique_ptr、std::mutex 或任何自定义 non-copyable 类型,编译器就会自动删除其拷贝构造函数。这不是语法错误,是类型系统对语义完整性的拒绝。
std::move_only_function 只要求 MoveConstructible,绕过拷贝检查,且底层不预留虚函数表项、不维护引用计数字段,实现零开销类型擦除。
- 能接住:
[ptr = std::make_unique<int>(42)]() mutable { return *ptr; }</int> - 不能直接接:
[x = 42]() { return x; }—— 即使它可拷贝,也必须显式std::move构造,否则模板参数推导失败 - 捕获引用如
[&x]看似合法,但std::move后原闭包失效,x成悬垂引用,运行时崩溃风险高
模板签名必须手写完整,不能省略任何限定符
std::move_only_function 不提供 make_move_only_function 辅助函数,也不支持 auto 推导。模板参数必须是完整函数类型,包括返回值、参数列表、noexcept、const、&、&& 等所有调用限定符。
- 正确:
std::move_only_function<int const std::string noexcept></int> - 错误:
std::move_only_function<void></void>—— 缺少括号和参数列表,编译直接报错 - 错误:
auto f = std::move_only_function{}—— 模板参数无法推导 - 即使被封装函数带
noexcept,而模板参数没写,operator()调用可能意外抛出,破坏异常安全预期
所有所有权转移操作都必须显式调用 std::move
编译器绝不会隐式转换左值为右值,也不会在运行时兜底。漏写 std::move 就是编译失败,没有“试一下”的机会。
- 存入
std::vector:std::vector<:move_only_function>> tasks;</:move_only_function>→ 必须写tasks.push_back(std::move(task)); - 函数传参:形参应声明为右值引用,例如
void enqueue(std::move_only_function<void> &&task)</void>;调用时必须传右值或显式std::move(task) - 类成员含
std::move_only_function时,移动赋值操作符不能依赖默认生成(除非所有成员都支持移动),需手动定义为= default或逐个std::move
空值检查和调用后状态管理最容易被忽略
std::move_only_function 支持空初始化,例如 std::move_only_function<void> f;</void> 是合法的,但调用前必须判空:if (f) f();。否则未初始化或已被 move 出的对象调用会触发未定义行为。
更关键的是:一旦对某个 std::move_only_function 对象执行了 std::move,它就进入有效但未指定状态(通常为空),再次调用或再次 move 都是未定义行为。这点比 std::function 严格得多,也容易被低估。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











