std::move_only_function是C++23中唯一能安全、零开销封装捕获std::unique_ptr等非拷贝资源的异步任务的类型;它仅要求MoveConstructible,绕过std::function强制CopyConstructible导致的编译失败,且避免shared_ptr引入的引用计数与语义冗余。

直接说结论:std::move_only_function 是 C++23 中唯一能安全、零开销地封装捕获 std::unique_ptr、std::mutex 或其他非拷贝资源的异步任务的类型;用 std::function 会编译失败,用 std::shared_ptr<:function></:function> 则引入不必要开销和语义混乱。
为什么 std::function 编译不过,而 std::move_only_function 可以
根本原因在约束条件不同:std::function 要求目标可拷贝(CopyConstructible),而捕获 std::unique_ptr 的 lambda 天然禁用拷贝构造——编译器报错不是你写错了,是语义冲突:
error: use of deleted function 'MyLambda::MyLambda(const MyLambda&)'
std::move_only_function 只要求 MoveConstructible 和 MoveAssignable,它不尝试复制,只接管所有权。只要 lambda 声明为 mutable(允许修改捕获的 unique_ptr),就能顺利存入。
立即学习“C++免费学习笔记(深入)”;
- 典型合法场景:[ptr = std::make_unique
(42)]() mutable { ptr.reset(); } - 非法但常见误写:[ptr = std::make_unique
(42)]() { return *ptr; } —— 缺少 mutable,无法移动内部ptr,仍可能被误判为“可拷贝”而塞不进std::move_only_function - 注意:即使 lambda 本身是
const限定的,只要它内部调用了移动操作(如std::move(ptr)),就只能走std::move_only_function
声明与初始化必须显式写出完整调用签名
std::move_only_function 没有 make_move_only_function 辅助函数,也不能靠 auto 推导,模板参数必须手写完整签名,包括 noexcept、&、const 等修饰符。
例如:
std::move_only_function<void()> task1; std::move_only_function<int(int, const std::string&) const noexcept> task2;
漏掉 const 或 noexcept,哪怕原 lambda 确实带这些限定,也会导致匹配失败或隐式转换被禁用。
- 错误写法:
std::move_only_function<void()> f = [x = std::make_unique<int>(0)]() { /* ... */ };—— lambda 默认不可调用(无mutable且捕获了 non-copyable),且签名不匹配 - 正确写法:
std::move_only_function<void()> f = [ptr = std::make_unique<int>(0)]() mutable { ptr.reset(); }; - 若 lambda 声明为
noexcept,签名里也必须写noexcept,否则赋值失败
往 vector 或传参时必须显式 std::move
std::vector<std::move_only_function<void()>> 是合法容器,但所有插入、赋值、传递都必须显式调用 std::move,否则编译器立刻拒绝——它不会给你运行时机会。
常见坑点:
-
tasks.push_back(task);→ 编译失败:试图拷贝 -
tasks.push_back(std::move(task));→ 正确,task进入有效但未指定状态,不能再调用 - 函数参数建议用右值引用:
void enqueue(std::move_only_function<void()>&& task),避免入口处就触发拷贝尝试 - 类成员含
std::move_only_function时,移动赋值操作符不能省略:需手动定义为= default或逐个std::move
别把它当成 std::function 的替代品,而是独占语义的守门人
它不提供 target() 或 target_type(),因为 move-only 类型擦除后无法安全还原原始类型;它也不保证小缓冲区一定内联——是否堆分配取决于 callable 大小与标准库实现(libc++ 默认 24 字节,libstdc++ 通常 32 字节)。
最容易被忽略的一点是:一旦 std::move_only_function 被 move 出去,原变量即失效;二次调用(哪怕是 std::move(f)())属于未定义行为。这不是 bug,是设计契约:移交即放弃。
如果需要多次执行,闭包内部自己管理状态(比如用 mutable + 成员变量),而不是靠外部反复 move。


















