应改用 std::move_only_function:因捕获 move-only 类型的 lambda 不满足 std::function 要求的 copyconstructible,而 std::move_only_function 仅需 moveconstructible,且无虚表与引用计数,专为零开销异步任务设计。

遇到 error: use of deleted function 'X::X(const X&)' 就该换 std::move_only_function,不是 lambda 写错了,是 std::function 语义上根本不能接住它。
为什么 std::function 编译失败而 std::move_only_function 可以
根本不是语法或拼写问题,而是类型系统在构造那一刻就拒绝了:只要 lambda 捕获了 std::unique_ptr、std::mutex 或任何删掉了拷贝构造的类型,编译器自动禁用其拷贝构造函数;std::function 构造时强制要求 CopyConstructible,直接报错。而 std::move_only_function 只要求 MoveConstructible,绕过拷贝检查,且底层不预留虚函数表项、不维护引用计数字段——它是为“移交即放弃”的异步任务设计的零开销容器。
- 即使 lambda 声明为
const,只要内部执行std::move(ptr)或ptr.reset(),它仍是 move-only 类型 -
std::function对可拷贝 lambda 是安全的,但对 move-only lambda 是硬性拦截,没有运行时兜底 - 错误发生在模板实例化阶段,不是调用时,所以别想“先跑起来再 debug”
std::move_only_function 模板参数必须手写完整签名
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 const></void>、std::move_only_function<double></double> - 错误:
std::move_only_function<void></void>(缺括号和参数列表) - 错误:
auto f = std::move_only_function{}(模板参数无法推导) - 若被封装函数带
noexcept,但模板参数没写,operator()()调用可能意外抛出异常,破坏调用方预期
所有所有权转移操作必须显式 std::move
编译器绝不会隐式把左值转成右值去调用移动构造;任何涉及“交出控制权”的动作,都必须手动加 std::move,否则直接编译失败。
- 初始化:
auto lambda = [ptr = std::make_unique<int>(42)]() mutable { return *ptr; };</int>→std::move_only_function<int> f = std::move(lambda);</int> - 存入容器:
std::vector<:move_only_function>> tasks;</:move_only_function>→tasks.push_back(std::move(task));,写tasks.push_back(task);编译失败 - 函数参数应声明为右值引用:
void enqueue(std::move_only_function<void> &&task)</void> - 若作为类成员,移动赋值操作符不能依赖默认生成,需显式定义为
= default或逐个对std::move_only_function成员调用std::move
调用前必须判空,且不支持 target() 还原原始类型
std::move_only_function 允许空初始化(std::move_only_function<void> f;</void>),但调用前必须用 if (f) 检查是否为空;它主动舍弃了 target() 和 target_type() 方法,因为 move-only 类型擦除后无法安全还原原始类型。
- 遍历执行前需逐个判空:
for (auto& f : tasks) { if (f) f(); } - 无法通过
f.target<decltype>()</decltype>取回原始闭包,这不是遗漏功能,而是设计取舍 - 这种限制也意味着你不能在运行时做类型反射或动态 dispatch,得靠静态接口契约保证行为一致
最容易被忽略的是:它不提供任何运行时兜底机制——漏写 std::move、少写一个 noexcept、忘了判空,都是编译失败或未定义行为,没有警告,也没有 fallback 路径。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











