std::move_only_function是唯一能安全包装捕获non-copyable类型lambda的标准工具,因它仅要求MoveConstructible而不要求CopyConstructible,但不支持拷贝、target()等操作且需显式声明签名。

std::move_only_function 不是用来凑数的“另一个 function”,它是唯一能接住捕获了 std::unique_ptr、std::mutex 或自定义 non-copyable 类型的 lambda 的标准工具——其他方式要么编译失败,要么运行时崩溃。
为什么 std::function 编译不过,而 std::move_only_function 可以
根本原因是约束不同:std::function 要求被包装类型满足 CopyConstructible;而捕获 std::unique_ptr 的 lambda 会自动删除拷贝构造函数,导致 std::function 直接拒收。错误信息通常是:
error: use of deleted function 'MyLambda::MyLambda(const MyLambda&)'
std::move_only_function 只要求 MoveConstructible,绕过拷贝检查,但代价是:它不提供拷贝能力,也不假装有。
- 这不是你写错了 lambda,是语义冲突——编译器在构造时就拒绝,不给你运行机会
- 即使 lambda 声明为
const,只要内部调用了std::move(ptr)(比如mutablelambda),它仍是 move-only - 别指望用
std::function+std::move临时糊弄,那只是把问题推迟到复制发生点,依然报错
声明和初始化必须显式写出完整签名
std::move_only_function 没有 make_move_only_function,也不能靠 auto 推导模板参数。漏掉任何修饰符(noexcept、const、&、&&)都会导致类型不匹配。
立即学习“C++免费学习笔记(深入)”;
正确写法示例:
std::move_only_function<int(int, const std::string&) noexcept> f = [ptr = std::make_unique<int>(42)](int x, const std::string& s) mutable noexcept {
return x + *ptr + s.size();
};
- 错误写法:
auto f = std::move_only_function<>{};(模板参数无法推导) - 错误写法:
std::move_only_function f = lambda;(缺少模板参数,编译失败) - 空初始化合法:
std::move_only_function<void()> f;,但调用前必须用if (f)检查是否为空
往 vector 存或传参时必须显式 std::move
所有涉及“拥有权转移”的操作,都必须手动调用 std::move。编译器不会帮你隐式移动,也不会给你运行时提示——直接编译失败。
-
std::vector<std::move_only_function<void()>> tasks;→tasks.push_back(std::move(task));,不能写tasks.push_back(task); - 函数参数建议用右值引用:
void enqueue(std::move_only_function<void()>&& task),避免在入口就尝试拷贝 - 若封装进类成员,移动赋值操作符需显式定义为
= default或逐个std::move成员 - 别把它塞进需要
CopyConstructible的老接口(如某些第三方回调注册函数),编译不过就是不过,没商量余地
捕获引用时容易忽略的运行时风险
如果 lambda 捕获的是引用(如 [&x]),std::move 后原闭包仍持有悬垂引用——这不会在编译时报错,但运行时崩。这不是 std::move_only_function 的 bug,而是你把生命周期管理交给了它,却没管好源头。
- 正确姿势:
auto task = std::move_only_function<void()>{[handle = std::move(socket)]() mutable { handle.send("done"); }}; - 错误姿势:
[&socket]() { socket.send(...); }→std::move后socket可能已析构 - 调用后原变量进入有效但未指定状态,再次调用(哪怕
std::move(task)())都是未定义行为
最常被忽略的一点:它不支持 target() 和 target_type()——不是忘了加,是主动舍弃。因为 move-only 类型擦除后无法安全还原原始类型,强行暴露接口反而误导使用者。


















