std::move_only_function是C++23引入的仅移动函数包装器,专为捕获unique_ptr等非拷贝类型的lambda设计,要求MoveConstructible而非CopyConstructible,模板参数需完整声明调用签名。

它不能替代 std::function,但当你手握一个捕获了 std::unique_ptr、std::mutex 或自定义非拷贝类型的 lambda 时,std::move_only_function 是唯一能合法收容它的标准容器。
为什么 std::function 编译失败而 std::move_only_function 可以
典型错误是 error: use of deleted function 'X::X(const X&)' ——这不是你写错了,而是语义冲突:lambda 捕获了 std::unique_ptr,自动禁用拷贝构造;std::function 要求目标满足 CopyConstructible,直接拒收;std::move_only_function 只要求 MoveConstructible,绕过该约束。
常见误判场景:
- 以为“只是换个包装”,结果把可拷贝 lambda(如
[x=42]() { return x; })直接赋给std::move_only_function,编译器会抱怨“无法从左值初始化”,必须显式std::move或用大括号初始化 - 在 const 成员函数里调用
std::move_only_function对象,却没在模板参数中加const限定,导致operator()()不可调用 - 试图对空的
std::move_only_function调用operator(),行为未定义(和std::function一样,但更少人检查)
声明和初始化必须显式写出完整签名
std::move_only_function 没有 make_move_only_function 辅助函数,也不支持 auto 推导。模板参数必须包含调用约定细节,否则编译失败。
立即学习“C++免费学习笔记(深入)”;
正确写法示例:
std::move_only_function<int(int, int)> f1{ [](int a, int b) { return a + b; } };
std::move_only_function<void() const noexcept> f2{ []() noexcept { /* ... */ } };
std::move_only_function<double() &&> f3{ []() && { return 3.14; } }; // 注意 &&
容易漏掉的点:
- noexcept、
&、&&、const都是签名一部分,缺一不可 - 捕获了
std::unique_ptr的 lambda 必须声明为mutable,否则移动捕获对象失败 - 不能写
std::move_only_function<void>—— 模板参数必须是完整函数类型,如void()
往 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),避免入口就尝试拷贝 - 类成员含
std::move_only_function时,移动赋值操作符需手动定义为= default或显式移动每个成员 - 别把它塞进老接口(如某些第三方回调注册函数),只要接口要求
CopyConstructible,编译不过就是不过
性能与兼容性的真实代价
空 std::move_only_function 通常占 16 字节(两个指针),比 std::function 的 32 字节小,但这只是表象——关键在于它彻底省掉了拷贝逻辑所需的虚函数表项、引用计数、SBO 对齐填充和析构守卫。
但要注意:
- 小 callable(如无捕获 lambda)可能原地构造,不触发堆分配;一旦超过小缓冲区容量(libc++ 默认 24 字节,libstdc++ 通常 32 字节),仍会堆分配
- 移动构造开销接近
memcpy,但调用开销和std::function相同 - C++23 才正式加入标准;GCC 13+ / Clang 16+ / MSVC 19.35+ 支持;旧编译器只能退回到
std::function+ 手动管理或第三方替代(如folly::Function)
最常被忽略的一点:它不提供 target() 或 target_type(),因为 move-only 类型擦除后无法安全还原原始类型——如果你依赖反射做调试或类型分发,得另想办法。


















