应使用std::move_only_function替代std::function来存储不可拷贝的lambda,它仅支持移动语义、零开销类型擦除,且要求显式声明签名、显式移动构造、容器操作需std::move、函数参数用右值引用、类成员需显式定义移动赋值、调用前必须判空。

如果您在C++23中尝试将捕获std::unique_ptr、std::mutex或自定义non-copyable类型的lambda存入函数对象容器,却遭遇编译错误,则很可能是因std::function强制要求CopyConstructible而拒绝该类型。以下是处理此类非拷贝任务的多种方法:
一、使用std::move_only_function显式声明并移动构造
std::move_only_function专为MoveConstructible类型设计,绕过拷贝约束,且不预留虚函数表项或引用计数字段,实现零开销类型擦除。它仅接受可移动、不可拷贝的可调用对象,且所有权转移必须显式进行。
1、声明时必须写出完整调用签名,包括参数列表、noexcept、const、&、&&等限定符;例如:std::move_only_function
2、初始化时必须使用std::move对源lambda执行显式移动;例如:auto lambda = [ptr = std::make_unique
3、禁止省略模板参数或使用auto推导;例如:auto f = std::move_only_function{};和std::move_only_function f = lambda;均为非法写法。
二、向容器(如std::vector)插入时强制显式std::move
std::move_only_function对象无法被拷贝,因此所有涉及所有权转移的操作(如push_back、emplace_back、赋值)均需手动调用std::move。编译器不会隐式转换,也不会提供运行时兜底——漏写即编译失败。
1、声明容器时指定完整签名;例如:std::vector<:move_only_function>> tasks;。
2、插入单个任务时必须包裹std::move;例如:tasks.push_back(std::move(task));。
3、禁止直接传入左值;例如:tasks.push_back(task);将触发编译错误error: use of deleted function。
三、作为函数参数传递时采用右值引用形参
为避免在函数入口处触发拷贝尝试,接收std::move_only_function的函数应声明为右值引用参数。这能确保调用方明确承担移动语义责任,并防止意外拷贝发生。
1、定义函数时使用&&修饰参数;例如:void enqueue(std::move_only_function
2、调用时传入右值或显式std::move的左值;例如:enqueue(std::move(task));或enqueue(std::move_only_function
3、禁止以值传递方式声明参数;例如:void enqueue(std::move_only_function
四、类成员中使用时显式定义移动赋值操作符
当std::move_only_function作为类数据成员时,编译器生成的默认移动赋值操作符可能不满足要求,尤其在存在其他非平凡可移动成员时。必须显式声明为= default或逐个std::move成员,以确保所有权正确转移。
1、在类定义中声明移动赋值操作符;例如:MyTask& operator=(MyTask&&) = default;。
2、若需自定义行为,则在实现中逐个移动成员;例如:other.task_ = std::move(task_);。
3、禁止依赖隐式生成的移动赋值操作符而不检查其有效性;例如:class MyTask { std::move_only_function
五、调用前必须判空并管理调用后状态
std::move_only_function支持空值初始化,但operator()对空对象调用是未定义行为。同时,一旦被std::move转移,原对象进入有效但未指定状态,再次调用将引发未定义行为。
1、声明空对象合法;例如:std::move_only_function
2、调用前必须显式检查;例如:if (f) f();。
3、move后原变量不可再调用;例如:auto g = std::move(f); f(); // 未定义行为。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











