应直接用 std::move_only_function 替代 std::function 处理 move-only 类型捕获的 lambda;因 std::function 要求 copyconstructible,而捕获 unique_ptr 等类型会使 lambda 拷贝构造被删除,std::move_only_function 仅需 moveconstructible,实现零开销类型擦除。

直接用 std::move_only_function 替代 std::function,别试图绕开它——只要 lambda 捕获了 std::unique_ptr、std::mutex 或任何 non-copyable 类型,std::function 就会在构造时编译失败,不是写法问题,是语义冲突。
为什么 std::function 编译失败而 std::move_only_function 可以
错误信息通常是 error: use of deleted function 'X::X(const X&)'。这不是你的 lambda 写错了,而是 std::function 强制要求被包装类型满足 CopyConstructible;一旦捕获 std::unique_ptr,lambda 的拷贝构造函数就被隐式删除。std::move_only_function 只要求 MoveConstructible,不预留虚函数表项、不引入引用计数,实现零开销类型擦除。
- 即使 lambda 声明为
const,只要内部调用std::move(ptr)或ptr.reset(),它就自动变成 move-only,只能进std::move_only_function - 捕获
[&x]的 lambda 本身可能仍可拷贝,但std::move后原闭包失效,x成为悬垂引用,运行时崩溃风险极高 -
std::move_only_function不提供target()或target_type(),因为 move-only 类型擦除后无法安全还原原始类型
std::move_only_function 模板参数必须显式写出完整签名
std::move_only_function 不支持 auto 推导,也没有 make_move_only_function 辅助函数。漏写任一调用限定符(如 noexcept、const、&、&&)都会导致类型不匹配,operator() 行为异常或编译失败。
- 正确写法:
std::move_only_function<int std::string int const noexcept></int> - 错误写法:
std::move_only_function<void></void>(缺少括号与参数列表) - 错误写法:
auto f = std::move_only_function{[](){}};(模板参数无法推导) - 若 lambda 声明为
noexcept,但模板参数未标注,调用时可能意外抛出异常,破坏异常安全契约
所有所有权转移操作都必须显式调用 std::move
编译器不会隐式转换右值,也不会在运行时兜底——漏写 std::move 就是立刻编译失败,不是警告,不是运行时崩溃。
- 初始化:
std::move_only_function<void> f = std::move(lambda);</void>,不能写f = lambda; - 存入容器:
std::vector<:move_only_function>> tasks;</:move_only_function>→tasks.push_back(std::move(task));,写tasks.push_back(task);直接报错 - 函数传参:形参应为
std::move_only_function<void>&&</void>,调用时必须传std::move(task),传左值会触发拷贝尝试并失败 - 作为类成员时,移动赋值操作符需显式定义(
= default或手动std::move成员),否则默认生成的版本可能不满足要求
调用前必须判空,且不能用于老式 CopyConstructible 接口
std::move_only_function 支持空状态,构造后可为 null,直接调用未检查的实例会导致未定义行为。更关键的是,它无法塞进任何依赖 CopyConstructible 的旧接口(比如某些第三方回调注册函数),编译不过就是不过,没有妥协路径。
- 合法空初始化:
std::move_only_function<void> f;</void>,但调用前必须写if (f) f(); - lambda 捕获引用时的悬垂风险极易被忽略:
std::move不影响引用本身的有效性,只移交闭包所有权;原变量 move 后仍持有对已销毁对象的引用 - 不要把它和
std::shared_ptr<:function></:function>混用——后者引入不必要的引用计数开销,且语义冗余
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











