C++ 线程池submit无法传递不可拷贝对象的解决方法
问题根因
编译报错由两层设计缺陷叠加导致,和目标类本身不可拷贝无必然冲突:
- 原有submit实现强制对所有入参做值拷贝捕获:构造工作单元时使用的写法
[f,args...],会将每个传入参数按值拷贝的方式复制到lambda的捕获列表中。遇到std::unique_ptr、持有std::mutex的自定义类这类仅支持移动、无可用拷贝构造的类型时,就会触发已删除的拷贝构造函数,直接编译失败。 - 直接调用目标函数时流程通畅:
std::move(valuePtr)产生的右值直接匹配index_file中std::unique_ptr类型的形参,通过移动构造完成形参初始化,没有中间拷贝环节,因此功能正常。
适配改造方案
改造过程无需修改现有不可拷贝类的实现,也无需将业务成员函数调整为静态,仅需修正submit的参数传递逻辑、调整工作队列的存储类型即可:
- 修正submit的捕获逻辑,替换为完美转发+移动捕获实现,取消强制拷贝所有参数的行为
C++14及以上版本可直接利用lambda初始化捕获特性,改造后的核心逻辑如下:
template<typename F, typename... Args> void submit(F&& f, Args&&... args) { // 完美转发传入的函数与参数,以移动/拷贝匹配的方式初始化lambda捕获变量,避免强制值拷贝 auto work = [ f = std::forward<F>(f), ...args = std::forward<Args>(args) ]() mutable { // *必须添加mutable修饰,否则捕获变量为const属性,无法触发移动操作* // 执行任务时将捕获的函数、参数按右值传递,匹配目标函数的形参要求 std::move(f)( std::move(args)... ); }; // 以移动方式将任务入队,不做额外拷贝 work_queue.push( std::move(work) ); }
- 调整工作队列的存储元素类型
若原有线程池的工作队列采用std::function<void()>作为存储类型,即使lambda已经支持移动捕获,依然会触发编译错误:std::function对包装的callable有强制可拷贝构造的要求,无法承载仅可移动的任务对象。可将队列存储替换为同属仅可移动语义的任务类型,例如std::packaged_task<void()>:
// 替换原有的可拷贝任务队列定义 std::queue<std::packaged_task<void()>> work_queue;
该存储方式完全兼容仅可移动的任务对象,无需为业务类额外实现拷贝语义。
方案与现有约束的适配性
- 无需将
addMap定义为静态函数:任务链路通过移动捕获拿到Dispatcher实例后,工作线程执行任务时可直接通过实例指针调用非静态的addMap方法,无需调整成员函数的访问属性。 - 无需为Dispatcher实现拷贝语义:从submit入参、lambda捕获、到
index_file函数形参初始化,全链路采用移动语义完成对象构造,不会触发任何拷贝操作。哪怕Dispatcher内部持有std::mutex(天生不可拷贝),也可正常运行。
内容的提问来源于stack exchange,提问作者Diana Deer
相关产品推荐
相关产品推荐

