std::move_only_function专为仅可移动对象设计,不支持拷贝,要求MoveConstructible而非CopyConstructible,模板参数需显式声明完整调用签名,移动语义必须显式调用std::move。

std::move_only_function 不是“只读”,而是“只移”——它根本不能复制,连 const 对象也不行;声明时必须显式写出调用签名,且所有传值/存储都得靠 std::move。
为什么 std::function 会编译失败,而 std::move_only_function 可以?
当你写一个捕获 std::unique_ptr 的 lambda,它自动禁用拷贝构造——std::function 要求目标满足 CopyConstructible,直接拒绝编译;std::move_only_function 只要求 MoveConstructible,绕过该约束。
- 典型错误信息:
error: use of deleted function 'X::X(const X&)',不是你的代码写错,是语义不匹配 - 它不提供
target()或target_type(),因为 move-only 类型擦除后无法安全还原原始类型 - 即使 lambda 是
const限定的,只要它内部移动了捕获对象,就只能进std::move_only_function,不能进std::function
怎么正确声明和初始化 std::move_only_function?
模板参数必须显式写出完整调用签名(含 noexcept、&、const 等修饰),没有 make_move_only_function 辅助函数,也不能靠 auto 推导。
- 正确写法:
std::move_only_function<int const std::string> f = [ptr = std::make_unique<int>(42)](int x, const std::string& s) mutable { return x + *ptr + s.size(); };</int></int> - 错误写法:
auto f = std::move_only_function{...};(模板参数无法推导) - 错误写法:
std::move_only_function f = lambda;(缺少模板参数,编译失败) - 空初始化合法:
std::move_only_function<void> f;</void>,但调用前必须检查if (f)
往 vector 或作为函数参数传递时最容易踩什么坑?
所有涉及“拥有权转移”的地方,都必须显式 std::move;否则编译器立刻报错,不会给你运行时机会。
立即学习“C++免费学习笔记(深入)”;
-
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成员 - 别把它塞进需要
CopyConstructible的老接口(如某些第三方回调注册函数),编译不过就是不过,没商量余地
性能差异真的只是“更小一点内存”吗?
空 std::move_only_function 通常占 16 字节(两个指针),std::function 常为 32 字节;但这只是表象——关键在于它彻底省掉了拷贝逻辑所需的虚函数表项、引用计数、SBO 对齐填充和析构守卫。
- 小 callable(如无捕获 lambda)可能原地构造,不触发堆分配;但一旦超过小缓冲区容量(libc++ 默认 24 字节,libstdc++ 通常 32 字节),仍会堆分配
- 移动构造开销接近 memcpy,比
std::function的移动快,因后者要维护共享状态 - 调用开销相同:一次虚函数调用,没差别
- 真正零开销的前提是你确认 callable 大小可控,且不依赖
target()这类反射能力
最常被忽略的一点:它支持 const、&、noexcept 等限定符作为模板参数一部分,这意味着 operator() 的 cv/ref/noexcept 属性会被保留——如果你的 lambda 声明为 void() const noexcept,那对应 std::move_only_function<void const noexcept></void> 才能完美匹配,否则可能静默退化或编译失败。

















