C++11下如何向CTPL线程池传递含unique_ptr右值引用参数的函数
C++11环境下CTPL线程池提交带unique_ptr右值参数任务的编译问题
约束前提
- 严格遵循C11标准编译,不启用C14及以上版本的特性
- 业务对接限制:无法修改test1函数的原有参数签名
- 使用的线程池为vit-vit开源CTPL实现,对应头文件为
ctpl_stl.h
复现代码
#include "thread-pool.hpp" #include <iostream> #include <string> struct Mystruct { int a; int b; std::string c; }; void test1(int id, std::unique_ptr<Mystruct>&& ms, int a) { while (true) { std::cout << ms->c << std::endl; std::this_thread::sleep_for(std::chrono::seconds(a)); } } void test2(int id, std::string&& c, int a) { while (true) { std::cout << c << std::endl; std::this_thread::sleep_for(std::chrono::seconds(a)); } } int main() { ctpl::thread_pool p(10); Mystruct* ms = new Mystruct; std::unique_ptr<Mystruct> msp(ms); p.push(test1, msp, 10); p.push(test2, "this is the end", 10); p.push(test2, "hello from the other side", 5); return 0; }
运行表现
- 带
std::string右值参数的test2任务可以正常通过push接口提交、在线程池内正常执行 - 带
std::unique_ptr<Mystruct>右值参数的test1任务无法通过编译,报错信息如下:
no instance of overloaded function "ctpl::thread_pool::push" matches the argument list C2893 Failed to specialize function template 'std::future<unknown-type> ctpl::thread_pool::push(F &&,Rest &&...)' C2780 'std::future<unknown-type> ctpl::thread_pool::push(F &&)': expects 1 arguments - 3 provided C2672 'ctpl::thread_pool::push': no matching overloaded function found
根因分析
编译失败的核心原因有两点:
- 类型推导不匹配:提交test1时传入的msp是具名的
std::unique_ptr左值,CTPL原生push接口在模板参数推导时,会将该实参推导为std::unique_ptr<Mystruct>&左值类型,无法直接匹配test1要求的std::unique_ptr<Mystruct>&&右值引用形参。即便提交时显式加std::move转换为右值,老版本CTPL依赖std::bind做参数绑定的实现,会将传入的右值转为内部左值存储,实际调用test1时传入的仍是左值,依然无法匹配右值引用要求。- 可拷贝约束不满足:std::unique_ptr 是不可拷贝类型,老版本CTPL在打包任务时通过std::bind存储参数,会默认要求所有传入参数满足可拷贝要求,unique_ptr没有拷贝构造函数,天然无法满足该约束。
test2可以正常运行的原因是:传入的字符串字面量会在CTPL打包任务时隐式构造为临时
std::string右值,既可以匹配std::string&&的右值引用形参,也满足可拷贝的打包要求。
可落地的解决方案
方案1:提交侧无侵入适配(无需修改线程池和test1签名)
- 提交任务时,用一层无捕获lambda做类型包装,不直接把test1作为push的首个参数传入
- 具体提交写法:
int main() { ctpl::thread_pool p(10); std::unique_ptr<Mystruct> msp(new Mystruct); // 适配后的test1提交逻辑 p.push([](int id, std::unique_ptr<Mystruct> ptr, int a){ // 将按值接收的unique_ptr显式转换为右值,匹配test1的形参要求 test1(id, std::move(ptr), a); }, std::move(msp), 10); // 原有test2的提交逻辑不受影响 p.push(test2, "this is the end", 10); p.push(test2, "hello from the other side", 5); return 0; }
- 方案优势:
- 完全不改动test1的原有函数签名,满足对接约束
- 不需要调整CTPL线程池的核心实现,所有适配逻辑集中在任务提交侧
- 完全兼容C++11标准,无高版本语法依赖
方案2:最小改动适配现有CTPL实现
如果允许小幅调整线程池代码,可以修改push接口的参数存储逻辑:将原来基于std::bind的参数绑定方式,替换为直接将可调用对象和参数打包到std::packaged_task中,通过完美转发传递所有权,天然支持unique_ptr这类move-only类型。
方案3:替换兼容的线程池实现
可以选择原生基于完美转发、支持move-only类型任务提交的C++11兼容线程池,选型时需要避开依赖std::bind做参数存储的老版本实现,确保可以直接提交带右值要求的不可拷贝类型参数。
内容的提问来源于stack exchange,提问作者MH Alikhani
相关产品推荐
相关产品推荐

